Oop 方法链接-为什么它是一个好的实践,或者不是?
是对象方法返回对象本身以便为另一个方法调用结果的实践。像这样:Oop 方法链接-为什么它是一个好的实践,或者不是?,oop,fluent-interface,method-chaining,Oop,Fluent Interface,Method Chaining,是对象方法返回对象本身以便为另一个方法调用结果的实践。像这样: participant.addSchedule(events[1]).addSchedule(events[2]).setStatus('attending').save() 这似乎被认为是一个好的实践,因为它产生可读的代码或“流畅的接口”。然而,在我看来,它似乎打破了面向对象本身所隐含的对象调用符号——生成的代码并不表示对以前方法的结果执行操作,这是面向对象代码通常预期的工作方式: participant.getSchedule
participant.addSchedule(events[1]).addSchedule(events[2]).setStatus('attending').save()
这似乎被认为是一个好的实践,因为它产生可读的代码或“流畅的接口”。然而,在我看来,它似乎打破了面向对象本身所隐含的对象调用符号——生成的代码并不表示对以前方法的结果执行操作,这是面向对象代码通常预期的工作方式:
participant.getSchedule('monday').saveTo('monnday.file')
这种差异为“调用结果对象”的点表示法创造了两种不同的含义:在链接上下文中,上述示例将被解读为保存参与者对象,即使该示例实际上是为了保存getSchedule接收到的schedule对象
我理解这里的区别在于被调用的方法是否应该返回某些东西(在这种情况下,它将返回被调用对象本身以进行链接)。但这两种情况与符号本身并没有区别,只与所调用方法的语义有区别。当不使用方法链接时,我总能知道方法调用会对与前一次调用的结果相关的内容进行操作-通过链接,这种假设被打破,我必须对整个链进行语义处理,以了解被调用的实际对象是什么。例如:
participant.attend(event).setNotifications('silent').getSocialStream('twitter').postStatus('Joining '+event.name).follow(event.getSocialId('twitter'))
最后两个方法调用引用getSocialStream的结果,而前面的方法调用引用参与者。也许在上下文发生变化的情况下编写链是一种不好的做法(是吗?),但即使这样,你也必须不断地检查看起来相似的点链实际上是否保持在相同的上下文中,或者只对结果起作用
在我看来,虽然方法链接表面上确实产生可读代码,但重载点符号的含义只会导致更多的混乱。因为我不认为自己是编程大师,我认为这是我的错。strong>那么:我错过了什么?我是否理解方法链接有点错误?在某些情况下,方法链接是特别好,还是特别差?
旁注:我理解这个问题可以被理解为一个伪装成问题的意见陈述。然而,事实并非如此——我真的很想理解为什么链接被认为是一种好的实践,以及我认为它打破了固有的面向对象符号的错误之处。这似乎有点主观 方法链接并不是本质上好或坏的东西 可读性是最重要的
(也考虑有大量方法链接,如果事情发生变化会使事情变得非常脆弱)
,在我看来,方法链有点新奇。当然,它看起来很酷,但我看不出它有什么真正的优势 如何:someList.addObject("str1").addObject("str2").addObject("str3")
任何优于:
someList.addObject("str1")
someList.addObject("str2")
someList.addObject("str3")
当addObject()返回一个新对象时可能会出现例外,在这种情况下,未加限制的代码可能会有点麻烦,如:
someList = someList.addObject("str1")
someList = someList.addObject("str2")
someList = someList.addObject("str3")
编辑:在过去的10年里,我对这个问题的看法发生了变化。对于可变对象,我仍然看不到有多少好处,尽管它对于避免一点点重复很有用。但是现在我更喜欢不变性,方法链接是我做非破坏性更新的首选方式,我一直都在使用它。我同意这是主观的。在大多数情况下,我避免了方法链接,但最近我也发现了一种情况,它是正确的-我有一个方法,它接受了10个参数,并且需要更多,但在大多数情况下,您只需要指定几个。有了覆盖,这变得非常麻烦,速度非常快。相反,我选择了链接方法:
MyObject.Start()
.SpecifySomeParameter(asdasd)
.SpecifySomeOtherParameter(asdasd)
.Execute();
方法链接方法是可选的,但它使编写代码更容易(尤其是使用IntelliSense)。但请注意,这是一个孤立的案例,在我的代码中并不是通用的做法
关键是——在99%的情况下,如果没有方法链接,您可能会做得一样好,甚至更好。但是有1%的人认为这是最好的方法。马丁·福勒在这里进行了很好的讨论: 方法链接 什么时候使用它 方法链接可以增加很多 内部DSL的可读性 结果几乎成了一场战争 synonum适用于某些情况下的内部DSL 头脑。方法链接是最好的, 但是,当它与 与其他功能组合 方法链接尤其重要 对parent:等语法有效:= (这个|那个)*。使用不同的 方法提供了一种可读的 看看接下来是哪一个论点。 类似地,也可以使用可选参数 使用方法轻松跳过 锁链。强制性条款清单, 例如父项::=第一秒不存在 很好地运用了基本形式, 虽然它可以得到很好的支持 使用渐进式接口。大部分 我更喜欢嵌套函数的时间 对于那件事 方法的最大问题 链接是完成的问题。 虽然通常有变通办法 如果你遇到这种情况,你会过得更好 使用嵌套函数。嵌套 功能也是一个更好的选择,如果 你正和我搞得一团糟 上下文变量
这是危险的,因为您可能依赖的对象比预期的多,例如,您的调用返回另一个类的实例: 我会举一个例子:
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 );
foodStore是由您拥有的许多食品商店组成的对象。
getLocalStore()返回一个对象,该对象包含与参数最近的存储的信息。getPriceforProduct(任何东西)是该对象的一个方法
因此,当您调用foodStore.getLocalStore(参数).getPriceforProduct(任何内容)时
您不仅依赖于FoodStore,而且还依赖于LocalStore
是否应该获得产品(任何东西)的价格
$this->db->select('something');
$this->db->from('table');
$this->db->where('id', $id);
foo.setHeight(100).setWidth(50).setColor('#ffffff');
foo.moveTo(100,100).highlight();
participant.getSchedule('monday').saveTo('monnday.file')
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
Dim loc As New HomeLocation()
loc.X(1337)
PrintLocation(loc)
PrintLocation(New HomeLocation().X(1337))
Public Class Dummy
Private _locA As New Location()
Public Sub New()
_locA.X(1337)
End Sub
End Class
Public Class Dummy
Private _locC As Location = New Location().X(1337)
End Class
New Dealer.CarPicker().Subaru.WRX.SixSpeed.TurboCharged.BlueExterior.GrayInterior.Leather.HeatedSeats
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)
collection.orderBy("column").limit(10);
collection = collection.orderBy("column").limit(10);
myText = myText.trim().toUpperCase();
page1 = collection.limit(10);
page2 = collection.offset(10).limit(10);
painting = canvas.withBackground('white').withPenSize(10);
var p = participant; // create a reference
p.addSchedule(events[1]);p.addSchedule(events[2]);p.setStatus('attending');p.save()
1. SINGLE-WORD
2. PARAMETERISED-WORD parameter
3. WORD1 [ OPTIONAL-WORD]
4. WORD2 { WORD-CHOICE-A | WORD-CHOICE-B }
5. WORD3 [ , WORD3 ... ]
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()
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 );
A.method1().method2().method3(); // one A
A.method1();
A.method2();
A.method3(); // repeating A 3 times
participant
.addSchedule(events[1])
.addSchedule(events[2])
.setStatus('attending')
.save();
participant.addSchedule(events[1]);
participant.addSchedule(events[2]);
participant.setStatus('attending');
participant.save()
participant
.getSchedule('monday')
.saveTo('monnday.file');
mondaySchedule = participant.getSchedule('monday');
mondaySchedule.saveTo('monday.file');
participant
.attend(event)
.setNotifications('silent')
.getSocialStream('twitter')
.postStatus('Joining '+event.name)
.follow(event.getSocialId('twitter'));
participant.attend(event);
participant.setNotifications('silent')
twitter = participant.getSocialStream('twitter')
twitter.postStatus('Joining '+event.name)
twitter.follow(event.getSocialId('twitter'));
import Participant
import Schedule
Participant participant = new Participant()
... snip...
Schedule s = participant.getSchedule(blah)
s.saveTo(filename)