Ila*_*ste 147 oop fluent-interface method-chaining
方法链接是返回对象本身的对象方法的实践,以便为另一个方法调用结果.像这样:
participant.addSchedule(events[1]).addSchedule(events[2]).setStatus('attending').save()
Run Code Online (Sandbox Code Playgroud)
这似乎被认为是一种很好的做法,因为它产生可读代码或"流畅的界面".但是,对我来说,它似乎打破了对象方向本身隐含的对象调用符号 - 结果代码并不表示对前一个方法的结果执行操作,这通常是预期面向对象的代码的工作方式:
participant.getSchedule('monday').saveTo('monnday.file')
Run Code Online (Sandbox Code Playgroud)
这种差异设法为"调用结果对象"的点符号创建两种不同的含义:在链接的上下文中,上面的示例将读作保存参与者对象,即使该示例实际上是为了保存时间表getSchedule收到的对象.
我知道这里的区别在于是否应该调用被调用的方法返回某些东西(在这种情况下,它会返回被调用对象本身以进行链接).但是这两种情况与符号本身无法区分,只能从被调用方法的语义中区分出来.当不使用方法链接时,我总是可以知道方法调用对与前一个调用的结果相关的操作进行操作- 使用链接,这个假设会中断,并且我必须在语义上处理整个链以了解实际的对象是什么真的叫.例如:
participant.attend(event).setNotifications('silent').getSocialStream('twitter').postStatus('Joining '+event.name).follow(event.getSocialId('twitter'))
Run Code Online (Sandbox Code Playgroud)
最后两个方法调用引用getSocialStream的结果,而之前引用的是参与者.也许在上下文发生变化的情况下实际编写链是不好的做法(是吗?),但即使这样,你也必须不断检查看起来相似的点链是否实际上保持在同一个上下文中,或者只对结果起作用.
对我来说似乎虽然表面链接表面确实产生可读代码,但重写点符号的含义只会导致更多的混淆.因为我不认为自己是编程大师,我认为错误是我的.那么:我错过了什么?我是否理解方法链以某种方式错误?在某些情况下,方法链接特别好,还是某些特别糟糕的情况?
旁注:我理解这个问题可以理解为一个被掩盖为问题的意见陈述.然而,它并非如此 - 我真的想要理解为什么链接被认为是良好的实践,以及我认为它打破固有的面向对象的符号在哪里出错.
RAY*_*RAY 76
只需2美分;
方法链接使调试变得棘手: - 你不能把断点放在一个简洁的点上,这样你就可以把程序准确地暂停到你想要的位置 - 如果其中一个方法抛出异常,你得到一个行号,你就不知道了"链"中的哪种方法引起了问题.
我认为总是写出非常短而简洁的线条通常是一种好习惯.每一行都应该只进行一次方法调用.更喜欢更长的线条.
编辑:评论提到方法链接和换行是分开的.那是真实的.但是,根据调试器的不同,可能会也可能无法在语句中间放置断点.即使可以,使用带有中间变量的单独行也可以提供更多的灵活性和一大堆值,您可以在Watch窗口中查看这些值以帮助调试过程.
Vil*_*lx- 71
我同意这是主观的.在大多数情况下,我避免使用方法链接,但最近我还发现了一个正确的情况 - 我有一个接受类似10个参数的方法,并且需要更多,但是在大多数时候你只需要指定一个少数.随着覆盖,这变得非常麻烦,非常快.相反,我选择了链接方法:
MyObject.Start()
.SpecifySomeParameter(asdasd)
.SpecifySomeOtherParameter(asdasd)
.Execute();
Run Code Online (Sandbox Code Playgroud)
这就像工厂模式.方法链接方法是可选的,但它使编写代码更容易(特别是使用IntelliSense).请注意,这是一个孤立的案例,并不是我的代码中的一般做法.
关键是 - 在99%的情况下,如果没有方法链接,你可以做得更好甚至更好.但是这是最佳方法的1%.
Bri*_*kau 38
就个人而言,我更喜欢仅对原始对象起作用的链接方法,例如设置多个属性或调用实用程序类型方法.
foo.setHeight(100).setWidth(50).setColor('#ffffff');
foo.moveTo(100,100).highlight();
Run Code Online (Sandbox Code Playgroud)
在我的示例中,当一个或多个链接方法返回除foo之外的任何对象时,我不使用它.虽然在语法上你可以链接任何东西,只要你在链中使用正确的API,更改对象IMHO会使事情变得不那么容易,如果不同对象的API有任何相似之处,可能会让人感到困惑.如果你做一些非常常见的方法调用末(.toString(),.print(),等等),其对象是你最终作用在?随便读取代码的人可能不会发现它将是链中隐式返回的对象而不是原始引用.
链接不同的对象也可能导致意外的null错误.在我的例子中,假设foo有效,所有方法调用都是"安全的"(例如,对foo有效).在OP的例子中:
participant.getSchedule('monday').saveTo('monnday.file')
Run Code Online (Sandbox Code Playgroud)
...无法保证(作为外部开发人员查看代码)getSchedule实际上将返回一个有效的非null调度对象.此外,调试这种代码风格通常要困难得多,因为许多IDE不会在调试时将方法调用评估为您可以检查的对象.IMO,任何时候您可能需要一个对象来检查以进行调试,我更喜欢将它放在一个显式变量中.
Dir*_*mar 23
Martin Fowler在这里有一个很好的讨论:
什么时候使用它
方法链接可以增加内部DSL的可读性,因此在某些人的思想中几乎成为内部DSL的同步.方法链接最好,当它与其他功能组合一起使用时.
方法链接对于像parent :: =(this | that)*这样的语法特别有效.使用不同的方法提供了可见的方式来查看下一个参数.类似地,可以使用Method Chaining轻松跳过可选参数.强制子句列表(例如parent :: = first second)对基本表单的效果不佳,尽管使用渐进式接口可以很好地支持它.大多数时候我更喜欢这种情况下的嵌套函数.
方法链的最大问题是整理问题.虽然有解决方法,但通常如果遇到这种情况,最好使用嵌套函数.如果您遇到上下文变量的混乱,嵌套函数也是更好的选择.
Tom*_*ing 20
在我看来,方法链接有点新奇.当然,它看起来很酷,但我没有看到任何真正的优势.
怎么:
someList.addObject("str1").addObject("str2").addObject("str3")
Run Code Online (Sandbox Code Playgroud)
比任何更好:
someList.addObject("str1")
someList.addObject("str2")
someList.addObject("str3")
Run Code Online (Sandbox Code Playgroud)
例外情况可能是addObject()返回一个新对象,在这种情况下,未链接的代码可能会更麻烦,如:
someList = someList.addObject("str1")
someList = someList.addObject("str2")
someList = someList.addObject("str3")
Run Code Online (Sandbox Code Playgroud)
这很危险,因为你可能依赖于比预期更多的对象,就像你的调用返回另一个类的实例一样:
我举个例子:
foodStore是一个由您拥有的许多食品商店组成的对象.foodstore.getLocalStore()返回一个对象,该对象保存与参数最近的存储的信息.getPriceforProduct(anything)是该对象的一种方法.
所以当你调用foodStore.getLocalStore(参数)时.getPriceforProduct(任何东西)
你不仅依赖于FoodStore,还依赖于LocalStore.
如果getPriceforProduct(任何东西)发生了变化,你不仅需要更改FoodStore,还需要更改调用链式方法的类.
你应该始终瞄准课堂之间的松耦合.
话虽这么说,我个人喜欢在编程Ruby时链接它们.
许多人使用方法链接作为一种方便的形式,而不是考虑到任何可读性问题.如果方法链接涉及对同一对象执行相同的操作,则可以接受方法链接 - 但前提是它实际上增强了可读性,而不仅仅是编写更少的代码.
不幸的是,许多人根据问题中给出的示例使用方法链接.虽然他们能仍然是可读的,他们是不幸的是造成多个类之间的高耦合,所以这是不可取的.
链接的好处,
即我喜欢使用它的地方
我没有看到提到的链接的一个好处是能够在变量启动期间使用它,或者在将新对象传递给方法时,不确定这是否是不好的做法.
我知道这是一个人为的例子,但是你说你有以下课程
Public Class Location
Private _x As Integer = 15
Private _y As Integer = 421513
Public Function X() As Integer
Return _x
End Function
Public Function X(ByVal value As Integer) As Location
_x = value
Return Me
End Function
Public Function Y() As Integer
Return _y
End Function
Public Function Y(ByVal value As Integer) As Location
_y = value
Return Me
End Function
Public Overrides Function toString() As String
Return String.Format("{0},{1}", _x, _y)
End Function
End Class
Public Class HomeLocation
Inherits Location
Public Overrides Function toString() As String
Return String.Format("Home Is at: {0},{1}", X(), Y())
End Function
End Class
Run Code Online (Sandbox Code Playgroud)
并且说你没有访问基类,或者说默认值是动态的,基于时间等等.是的你可以实例化然后更改值但是这会变得很麻烦,特别是如果你只是通过方法的值:
Dim loc As New HomeLocation()
loc.X(1337)
PrintLocation(loc)
Run Code Online (Sandbox Code Playgroud)
但这不是更容易阅读:
PrintLocation(New HomeLocation().X(1337))
Run Code Online (Sandbox Code Playgroud)
或者,一个班级成员呢?
Public Class Dummy
Private _locA As New Location()
Public Sub New()
_locA.X(1337)
End Sub
End Class
Run Code Online (Sandbox Code Playgroud)
VS
Public Class Dummy
Private _locC As Location = New Location().X(1337)
End Class
Run Code Online (Sandbox Code Playgroud)
这就是我一直在使用链接的方式,通常我的方法只是用于配置,所以它们只有2行长,然后设置一个值Return Me.对于我们来说,它已经清理了非常难以阅读的大行,并将代码理解为一行,就像句子一样.就像是
New Dealer.CarPicker().Subaru.WRX.SixSpeed.TurboCharged.BlueExterior.GrayInterior.Leather.HeatedSeats
Run Code Online (Sandbox Code Playgroud)
和某些东西一样
New Dealer.CarPicker(Dealer.CarPicker.Makes.Subaru
, Dealer.CarPicker.Models.WRX
, Dealer.CarPicker.Transmissions.SixSpeed
, Dealer.CarPicker.Engine.Options.TurboCharged
, Dealer.CarPicker.Exterior.Color.Blue
, Dealer.CarPicker.Interior.Color.Gray
, Dealer.CarPicker.Interior.Options.Leather
, Dealer.CarPicker.Interior.Seats.Heated)
Run Code Online (Sandbox Code Playgroud)
不利于链接,即我不喜欢使用它
当有很多参数传递给例程时,我不使用链接,主要是因为行变得非常长,并且正如OP所提到的,当你将例程调用到其他类传递给其中一个时,它会让人感到困惑.链接方法.
还有一个例程会返回无效数据的问题,因此到目前为止,当我返回被调用的同一个实例时,我只使用了链接.正如所指出的那样,如果你在类之间进行链接,那么你会更加努力地进行调试(哪一个返回null?)并且可以增加类之间的依赖关系.
结论
就像生活和编程中的一切一样,Chaining既不好也不坏,如果你可以避免坏,那么链接可以是一个很大的好处.
我试着遵循这些规则.
方法链接可以直接在Java中设计高级DSL.实质上,您可以至少建模这些类型的DSL规则:
1. SINGLE-WORD
2. PARAMETERISED-WORD parameter
3. WORD1 [ OPTIONAL-WORD]
4. WORD2 { WORD-CHOICE-A | WORD-CHOICE-B }
5. WORD3 [ , WORD3 ... ]
Run Code Online (Sandbox Code Playgroud)
可以使用这些接口实现这些规则
// Initial interface, entry point of the DSL
interface Start {
End singleWord();
End parameterisedWord(String parameter);
Intermediate1 word1();
Intermediate2 word2();
Intermediate3 word3();
}
// Terminating interface, might also contain methods like execute();
interface End {}
// Intermediate DSL "step" extending the interface that is returned
// by optionalWord(), to make that method "optional"
interface Intermediate1 extends End {
End optionalWord();
}
// Intermediate DSL "step" providing several choices (similar to Start)
interface Intermediate2 {
End wordChoiceA();
End wordChoiceB();
}
// Intermediate interface returning itself on word3(), in order to allow for
// repetitions. Repetitions can be ended any time because this interface
// extends End
interface Intermediate3 extends End {
Intermediate3 word3();
}
Run Code Online (Sandbox Code Playgroud)
使用这些简单的规则,您可以直接在Java中实现复杂的DSL,例如SQL,就像jOOQ(我创建的库)所做的那样.请参阅我的博客中的一个相当复杂的SQL示例:
create().select(
r1.ROUTINE_NAME,
r1.SPECIFIC_NAME,
decode()
.when(exists(create()
.selectOne()
.from(PARAMETERS)
.where(PARAMETERS.SPECIFIC_SCHEMA.equal(r1.SPECIFIC_SCHEMA))
.and(PARAMETERS.SPECIFIC_NAME.equal(r1.SPECIFIC_NAME))
.and(upper(PARAMETERS.PARAMETER_MODE).notEqual("IN"))),
val("void"))
.otherwise(r1.DATA_TYPE).as("data_type"),
r1.NUMERIC_PRECISION,
r1.NUMERIC_SCALE,
r1.TYPE_UDT_NAME,
decode().when(
exists(
create().selectOne()
.from(r2)
.where(r2.ROUTINE_SCHEMA.equal(getSchemaName()))
.and(r2.ROUTINE_NAME.equal(r1.ROUTINE_NAME))
.and(r2.SPECIFIC_NAME.notEqual(r1.SPECIFIC_NAME))),
create().select(count())
.from(r2)
.where(r2.ROUTINE_SCHEMA.equal(getSchemaName()))
.and(r2.ROUTINE_NAME.equal(r1.ROUTINE_NAME))
.and(r2.SPECIFIC_NAME.lessOrEqual(r1.SPECIFIC_NAME)).asField())
.as("overload"))
.from(r1)
.where(r1.ROUTINE_SCHEMA.equal(getSchemaName()))
.orderBy(r1.ROUTINE_NAME.asc())
.fetch()
Run Code Online (Sandbox Code Playgroud)
另一个很好的例子是jRTF,一个用于直接用Java来修改RTF文档的DSL.一个例子:
rtf()
.header(
color( 0xff, 0, 0 ).at( 0 ),
color( 0, 0xff, 0 ).at( 1 ),
color( 0, 0, 0xff ).at( 2 ),
font( "Calibri" ).at( 0 ) )
.section(
p( font( 1, "Second paragraph" ) ),
p( color( 1, "green" ) )
)
).out( out );
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
55428 次 |
| 最近记录: |