方法链接-为什么这是一个好习惯?


151

方法链接是对象方法返回对象本身以使结果被另一个方法调用的实践。像这样:

participant.addSchedule(events[1]).addSchedule(events[2]).setStatus('attending').save()

这似乎被认为是一种好习惯,因为它会产生可读的代码或“流畅的界面”。但是,对我而言,它似乎打破了面向对象本身所隐含的对象调用表示法-生成的代码不代表对先前方法的结果执行的动作,通常这是面向对象的代码的工作方式:

participant.getSchedule('monday').saveTo('monnday.file')

这种差异设法为“调用结果对象”的点标记创建了两种不同的含义:在链接的上下文中,以上示例将被视为保存参与者对象,即使该示例实际上旨在保存计划表也是如此。 getSchedule接收的对象。

我知道这里的区别在于是否应期望被调用方法返回某些内容(在这种情况下,它将返回被调用对象本身进行链接)。但是,这两种情况与符号本身并没有区别,仅与被调用方法的语义相区别。当不使用方法链接时,我总能知道方法调用是在与上一个调用的结果相关的操作上进行的-通过链接,这种假设会中断,并且我必须在语义上处理整个链,以了解实际对象是什么叫真的是。例如:

participant.attend(event).setNotifications('silent').getSocialStream('twitter').postStatus('Joining '+event.name).follow(event.getSocialId('twitter'))

那里的最后两个方法调用引用了getSocialStream的结果,而之前的方法引用了参与者。实际在上下文发生变化的地方写链可能不是一个好习惯(是吗?),但是即使如此,您也必须不断检查看起来相似的点链实际上是否在同一个上下文中,或者仅在结果上起作用。

在我看来,虽然表面上的方法链接确实会产生可读的代码,但点符号含义的重载只会导致更多的混乱。由于我不认为自己是编程专家,所以我认为这是我的错。所以:我想念什么?我是否理解方法链接有些错误?在某些情况下方法链接特别好还是在某些情况下特别糟糕?

旁注:我理解这个问题可以理解为掩盖为问题的意见陈述。但是,它不是-我真正地想了解为什么链被认为是一种好习惯,而在认为链打破了固有的面向对象表示法的地方,我犯了错。


似乎方法链接至少是在Java中编写智能代码的一种方法。即使不是每一个同意..
默瑟Traieste

他们也称这些“流利”方法或“流利”界面。您可能需要更新标题以使用此术语。
S.Lott

4
在另一篇SO讨论中,有人说过流畅的接口是一个更大的概念,与代码的可读性有关,方法链接只是实现这一目标的一种方法。但是,它们紧密相关,因此我确实在文本中添加了标记和引用的流畅接口-我认为这些就足够了。
Ilari Kajaste 2009年

14
我想到这一点的方法是,实际上,方法链接是一种使语言语法缺少功能的变通方法。如果有一个内置的替代标记.,它将忽略任何方法返回的值,并始终使用同一对象调用任何链式方法,则实际上并不需要它。
Ilari Kajaste

这是一个很棒的编码习惯,但是像所有很棒的工具一样,它也被滥用。
马丁·斯帕默

Answers:


74

我同意这是主观的。在大多数情况下,我避免使用方法链接,但是最近我还发现了一种情况,那就是正确的做法-我有一个方法可以接受10个参数之类的参数,并且需要更多的参数,但是在大多数情况下,您只需要指定一个很少。使用覆盖时,这变得非常麻烦。相反,我选择了链接方法:

MyObject.Start()
    .SpecifySomeParameter(asdasd)
    .SpecifySomeOtherParameter(asdasd)
    .Execute();

方法链接方法是可选的,但是它使编写代码更容易(尤其是使用IntelliSense)。请注意,这只是一个孤立的案例,并不是我的代码中的常规做法。

关键是-在99%的情况下,无需方法链接就可以做得甚至更好。但这是1%的最佳方法。


4
IMO,在这种情况下使用方法链接的最佳方法是创建一个要传递给函数的参数对象,例如P = MyObject.GetParamsObj().SomeParameter(asdasd).SomeOtherParameter(asdasd); Obj = MyObject.Start(); MyObject.Execute(P);。您具有能够在其他调用中重用此参数对象的优点,这是一个加号!
pedromanoel 2012年

20
就我对模式的贡献而言,工厂方法通常只有一个创建点,并且产品是基于工厂方法的参数的静态选择。该链创建看起来更到您调用不同的方法来获得结果的Builder模式,方法可以是可选的,因为在方法链中,我们可以有这样的事PizzaBuilder.AddSauce().AddDough().AddTopping()更引用在这里
马可梅德拉诺

3
当方法链接(如原始问题中的chown)违反Demeter定律时,被认为是不好。参见:ifacethoughts.net/2006/03/07/…实际上,这里给出的答案遵循法律,因为它是“构建者模式”。
Angel O'Sphere 2014年

2
@Marco Medrano,因为我早在JavaWorld之前就读过它,所以PizzaBuilder的示例始终困扰着我。我觉得我应该在我的比萨饼上加调味料,而不是我的厨师。
Breandán道尔顿

1
我知道你的意思,维尔克斯。但是,当我阅读时list.add(someItem),我将其读为“此代码正在添加someItemlist对象中”。因此,当我阅读时PizzaBuilder.AddSauce(),我自然地将其读为“此代码为PizzaBuilder对象添加了调味料”。换句话说,我将协调器(进行添加的协调器)视为嵌入代码list.add(someItem)或的方法PizzaBuilder.addSauce()。但是我只是认为PizzaBuilder的示例有点虚构。您的榜样MyObject.SpecifySomeParameter(asdasd)对我来说很好。
Breandán道尔顿

78

只是我的2美分;

方法链接使调试变得棘手:-您不能将断点放在简明的位置,因此您可以将程序恰好停在所需的位置-如果这些方法之一引发异常,并且您获得了行号,则不知道“链”中的哪种方法引起了问题。

我认为通常最好总是写简短明了的代码。每行应该只进行一个方法调用。优先选择更多行而不是更长行。

编辑:评论提到方法链接和换行符是分开的。那是真实的。但是,取决于调试器,在语句的中间放置断点可能会也可能不会。即使可以,使用带有中间变量的单独行也可以为您提供更大的灵活性,并提供大量值,您可以在“监视”窗口中进行检查,以帮助调试过程。


4
使用换行符和方法链不是独立的吗?您可以按照@ Vilx- Answer的方式将每个调用链接到换行符上,并且通常可以在同一行上放置多个单独的语句(例如,在Java中使用分号)。
brabster

2
这是完全有道理的答复,但它只显示了我所知道的所有调试器中存在的弱点,并且与该问题没有特定关系。
masterxilo 2014年

1
@Brabster。对于某些调试器,它们可能是分开的,但是,在调查错误时,使用中间变量进行单独的调用仍然可以为您提供更多的信息。
2014年

4
+1,您永远不会知道在逐步调试方法链时每个方法返回什么。方法链模式是hack。这是维护程序员最糟糕的噩梦。
Lee Kowalkowski

1
我也不是链接的忠实拥护者,但为什么不简单地将断点放在方法定义中呢?
Pankaj Sharma '18

39

就个人而言,我更喜欢仅对原始对象起作用的链接方法,例如设置多个属性或调用实用程序类型的方法。

foo.setHeight(100).setWidth(50).setColor('#ffffff');
foo.moveTo(100,100).highlight();

当我的示例中一个或多个链接方法将返回foo以外的任何对象时,我不使用它。从句法上讲,只要您为链中的那个对象使用正确的API,就可以链接任何东西,但是更改对象IMHO会使事情变得不那么易读,并且如果不同对象的API有任何相似之处,则可能会造成混乱。如果你做一些非常常见的方法调用末(.toString().print(),等等),其对象是你最终作用在?随便阅读代码的人可能不会发现它是链中隐式返回的对象,而不是原始引用。

链接不同的对象也会导致意外的空错误。在我的示例中,假设foo有效,则所有方法调用都是“安全的”(例如,对foo有效)。在OP的示例中:

participant.getSchedule('monday').saveTo('monnday.file')

...无法保证(作为外部开发人员查看代码)getSchedule实际上会返回有效的非null调度对象。同样,调试这种类型的代码通常要困难得多,因为许多IDE不会在调试时将方法调用评估为可以检查的对象。IMO,任何时候您可能需要检查对象以进行调试时,我都希望将其放在显式变量中。


如果有可能a Participant没有有效的值,SchedulegetSchedule方法被设计为返回Maybe(of Schedule)类型,而saveTo方法被设计为接受Maybe类型。
莱特曼2015年

24

Martin Fowler在这里进行了很好的讨论:

方法链接

什么时候使用

方法链接可以大大提高内部DSL的可读性,因此在某些人看来,它几乎已成为内部DSL的重要组成部分。但是,将方法链接与其他功能组合一起使用时,它是最好的方法。

方法链接对于像parent :: =(this | that)*这样的语法特别有效。使用不同的方法提供了一种清晰易懂的方式,可查看接下来要使用的参数。同样,可以通过“方法链接”轻松跳过可选参数。强制性条款列表,例如parent :: =第一秒,虽然可以通过使用渐进式接口很好地支持,但在基本形式上效果不佳。在大多数情况下,在这种情况下,我更喜欢嵌套函数。

方法链接的最大问题是整理问题。尽管有解决方法,但通常如果遇到这种情况,最好使用嵌套函数。如果您对上下文变量感到困惑,则嵌套函数也是一个更好的选择。


2
链接已死:(
Qw3ry

DSL是什么意思?领域特定语言
索伦

@Sören:Fowler指的是特定领域的语言。
德克·沃尔玛

21

我认为,方法链接有点新颖。当然,它看起来很酷,但是我看不出任何真正的优势。

怎么:

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")

9
更为简洁,因为即使在第一个示例中,您也避免了两个“ someList”部分,并以一行而不是三行结束。现在,究竟是好是坏取决于不同的事情,也许只是口味问题。
Fabian Steeg

26
仅拥有“ someList”一次的真正好处是,给它一个更长,更具描述性的名称要容易得多。每当一个名称需要快速连续出现多次时,就有一种将其缩写的趋势(以减少重复并提高可读性),这使其名称的描述性降低,损害了可读性。
克里斯·多德

关于可读性和DRY原理的要点是要注意的一点,方法链可以防止对给定方法的返回值进行内省和/或暗示链中每个方法的空列表/集合和非空值的推定(导致谬误的原因)我必须经常调试/修复的系统中的许多NPE)。
Darrell Teague

@ChrisDodd从未听说过自动完成吗?
inf3rno

1
“人脑如果具有相同的缩进,就会非常擅长识别文本重复” –正是重复的问题:它使大脑专注于重复模式。因此,要阅读并理解代码,您必须迫使您的大脑在重复模式的后面/后面看,以查看真正发生的情况,这是大脑容易掩盖的重复模式之间的差异。这就是为什么重复对代码可读性不利。
克里斯·多德

7

这很危险,因为您可能依赖比预期更多的对象,例如您的调用将返回另一个类的实例:

我举一个例子:

foodStore是一个由您拥有的许多食品商店组成的对象。foodstore.getLocalStore()返回一个对象,该对象保存与参数最接近的商店的信息。getPriceforProduct(anything)是该对象的方法。

因此,当您调用foodStore.getLocalStore(parameters).getPriceforProduct(anything)

您不仅依赖于FoodStore,还依赖于LocalStore。

如果getPriceforProduct(任何内容)发生变化,则不仅需要更改FoodStore,还需要更改调用链式方法的类。

您应该始终以类之间的松散耦合为目标。

话虽如此,我个人喜欢在编程Ruby时将它们链接起来。


7

许多人将方法链接作为一种便利形式使用,而不是出于对可读性的考虑。如果方法链接涉及对同一对象执行相同的操作,则方法链接是可以接受的-但前提是它实际上提高了可读性,而不仅是编写更少的代码。

不幸的是,根据问题中给出的示例,许多人使用方法链接。虽然他们仍然是可读的,他们是不幸的是造成多个类之间的高耦合,所以这是不可取的。


6

这似乎有点主观。

方法链接并不是本质上不好或不好的东西。

可读性是最重要的。

(还应考虑,如果发生某些更改,将大量方法链接在一起会使事情变得非常脆弱)


它可能确实是主观的,因此是主观标签。我希望答案会向我强调,在这种情况下,方法链接将是一个好主意-现在我看不到任何东西,但是我认为这只是我未能理解该概念的优点,而不是链接本身固有的不良影响。
Ilari Kajaste 2009年

如果它导致高耦合,会不会天生不好?将链分成单独的语句不会降低可读性。
aberrant80

1
取决于你想要的教条。如果它使结果更具可读性,则在许多情况下可能更可取。这种方法的最大问题是,对象上的大多数方法将返回对该对象本身的引用,但通常该方法将返回对子对象的引用,您可以在该子对象上链接更多方法。一旦开始执行此操作,则另一个编码器很难解开正在发生的事情。同样,方法功能的任何更改都将是在大型复合语句中进行调试的痛苦。
约翰·尼古拉斯

6

链接的好处,
即我喜欢在哪里使用

我没有提到的链接的好处之一是能够在变量初始化过程中使用它,或者在将新对象传递给方法时使用它,这不确定是否是不好的做法。

我知道这是人为的例子,但说您有以下课程

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

这就是我一直使用链接的方式,通常我的方法仅用于配置,因此它们只有2行,请设置一个值,然后Return Me。对我们来说,它清除了很难阅读和理解代码的大行,变成了像句子一样的一行。就像是

New Dealer.CarPicker().Subaru.WRX.SixSpeed.TurboCharged.BlueExterior.GrayInterior.Leather.HeatedSeats

VS之类的

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)


不利于链接,即我不喜欢使用它的地方

当有很多参数要传递给例程时,我不使用链接,这主要是因为行变得很长,并且正如OP所述,当您将例程调用到其他类以传递给其中一个时,它可能会造成混乱链接方法。

还担心例程会返回无效数据,因此到目前为止,我只在返回相同实例时才使用链接。如前所述,如果您在类之间进行链接,则会使调试更加困难(哪一个返回null?),并且会增加类之间的依赖关系耦合。

结论

就像生活中的一切以及编程一样,如果不能避免不好的话,链接既不好也不坏,那么链接可以带来很大的好处。

我尝试遵循这些规则。

  1. 尽量不要在课程之间链接
  2. 制定专门用于链接的例程
  3. 在链接例程中只能做一件事情
  4. 提高可读性时使用
  5. 当它使代码更简单时使用它

6

方法链接可以允许直接用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 ... ]

这些规则可以使用这些接口来实现

// 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();
}

使用这些简单的规则,您可以直接在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()

另一个很好的例子是jRTF,这是一个小DSL,旨在直接用Java生成RTF文档。一个例子:

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 );

@ user877329:是的,它几乎可以在任何了解接口和子类型多态性的面向对象的编程语言中使用
Lukas Eder

4

在大多数情况下,方法链接可能只是一个新颖事物,但我认为它具有应有的地位。在CodeIgniter的Active Record用法中可以找到一个示例:

$this->db->select('something')->from('table')->where('id', $id);

看起来比以下内容更干净(我认为更有意义):

$this->db->select('something');
$this->db->from('table');
$this->db->where('id', $id);

这确实是主观的。每个人都有自己的见解。


这是带有方法链接的Fluent接口的示例,因此UseCase在这里稍有不同。您不仅在链接,而且还在创建易于阅读的内部特定于域的语言。在一个旁注中,CI的ActiveRecord不是ActiveRecord。
戈登

3

我认为主要的谬误是认为这通常是一种面向对象的方法,而实际上它比任何其他方法都更像是一种函数编程方法。

我使用它的主要原因是出于可读性和防止代码被变量淹没。

当别人说这会损害可读性时,我真的不太理解。这是我使用过的最简洁,最有凝聚力的编程形式之一。

还有这个:

convertTextToVoice.LoadText(“ source.txt”)。ConvertToVoice(“ destination.wav”);

是我通常会使用的方式。用它来链接x个参数不是我通常使用的方式。如果我想在一个方法调用中输入x个参数,我将使用params语法:

公共无效的foo(params object []个项目)

然后根据类型强制转换对象,或者仅根据您的用例使用数据类型数组或集合。


1
+1表示“主要谬误是,通常认为这是一种面向对象的方法,而实际上它比任何其他方法都更像是一种函数编程方法”。突出的用例是对对象进行无状态操作(在其中不更改其状态,而是返回一个继续操作的新对象)。OP的问题和其他答案显示了有状态的动作,这些动作确实看起来很尴尬。
OmerB

是的,您是对的,它是一种无状态操作,只是我通常不创建新对象,而是使用依赖项注入使其成为可用服务。是的,有状态用例不是我认为方法链接的目的。我看到的唯一例外是,如果使用某些设置初始化DI服务并具有某种监视程序(如某种COM服务)来监视状态。只是恕我直言。
Shane Thorndike

2

我同意,为此,我更改了在我的库中实现流利接口的方式。

之前:

collection.orderBy("column").limit(10);

后:

collection = collection.orderBy("column").limit(10);

在“之前”实现中,函数修改了对象并以结尾return this。我更改了实现以返回相同类型的新对象

我对此更改的理由

  1. 返回值与函数无关,它纯粹是在支持链接部分,根据OOP应该是void函数。

  2. 系统库中的方法链接也以这种方式实现(例如linq或string):

    myText = myText.trim().toUpperCase();
    
  3. 原始对象保持不变,从而允许API用户决定如何处理它。它允许:

    page1 = collection.limit(10);
    page2 = collection.offset(10).limit(10);
    
  4. 一个副本实现,也可以用于建筑物的对象:

    painting = canvas.withBackground('white').withPenSize(10);
    

    其中setBackground(color)功能改变实例,并返回什么(如它应该)

  5. 函数的行为更可预测(请参见第1点和第2点)。

  6. 使用简短的变量名还可以减少代码混乱,而无需在模型上强制使用api。

    var p = participant; // create a reference
    p.addSchedule(events[1]);p.addSchedule(events[2]);p.setStatus('attending');p.save()
    

结论:
我认为使用return this实现的流畅接口是错误的。


1
但是,是否不会为每个调用返回一个新实例会产生很多开销,特别是如果您使用的是较大的物品呢?至少对于非托管语言。
Apeiron

在性能方面,@ Apeiron绝对可以更快地进行return this管理。我认为它以“非自然”方式获得了流利的api。(添加原因6:显示一种非流利的替代方法,它没有开销/增加的功能)
Bob Fanger 2011年

我同意你的看法。最好还是保持DSL的基础状态不变,并在每次方法调用时返回新对象,而不是……我很好奇:您提到的那个库是什么?
卢卡斯·埃德

1

这里完全错过的一点是方法链接允许DRY。这是“ with”(在某些语言中实施不佳)的有效替代。

A.method1().method2().method3(); // one A

A.method1();
A.method2();
A.method3(); // repeating A 3 times

这与DRY始终重要的原因相同;如果结果是A错误,并且这些操作需要在B上执行,则只需在1处更新,而不是3。

实用上,这种情况下的优势很小。尽管如此,我还是要少打字一些,更坚固一些(DRY)。


14
在源代码中重复变量名称与DRY原理无关。DRY声明“每条知识在系统中必须具有单一,明确,权威的表示形式”,或者换句话说,应避免知识的重复(而不是文本的重复)。
pedromanoel 2012年

4
最肯定的是违反干燥的重复。重复变量名称(不必要地)会以与其他形式的dry相同的方式造成所有弊端:它创建更多的依赖项和更多的工作。在上面的示例中,如果我们重命名A,则湿版本将需要进行3次更改,如果错过了这3个版本中的任何一个,将导致调试错误。
Anfurny 2012年

1
我看不到您在此示例中指出的问题,因为所有方法调用都彼此接近。另外,如果您忘记在一行中更改变量的名称,编译器将返回一个错误,并且将在运行程序之前将其更正。同样,变量的名称仅限于其声明的范围。除非此变量是全局变量,否则这已经是不好的编程习惯。IMO,DRY并不是要减少打字,而是要保持事物隔离。
pedromanoel 2012年

“编译器”可能返回错误,或者您使用的是PHP或JS,而当您遇到这种情况时,解释器可能只会在运行时发出错误。
Anfurny 2012年

1

我通常讨厌方法链接,因为我认为它会降低可读性。紧凑性经常与可读性相混淆,但是它们不是同一术语。如果您在单个语句中执行所有操作,则这很紧凑,但是与在多个语句中执行操作相比,大多数情况下它的可读性(难于跟踪)低。如您所见,除非不能保证所用方法的返回值相同,否则方法链接将造成混乱。

1.)

participant
    .addSchedule(events[1])
    .addSchedule(events[2])
    .setStatus('attending')
    .save();

participant.addSchedule(events[1]);
participant.addSchedule(events[2]);
participant.setStatus('attending');
participant.save()

2.)

participant
    .getSchedule('monday')
        .saveTo('monnday.file');

mondaySchedule = participant.getSchedule('monday');
mondaySchedule.saveTo('monday.file');

3.)

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'));

如您所见,您几乎赢了,因为您必须在单条语句中添加换行符以使其更具可读性,并且必须添加缩进以清楚地表明您在谈论不同的对象。好吧,如果我想使用基于身份的语言,那么我将学习Python而不是这样做,更不用说大多数IDE会通过自动格式化代码来删除缩进。

我认为唯一可以使用这种链接的地方是在CLI中管道流或在SQL中将多个查询一起加入。两者都有多个语句的价格。但是,如果您想解决复杂的问题,即使是那些付出代价并使用变量在多条语句中编写代码,或者编写bash脚本和存储过程或视图的人,最终也将无法解决。

根据DRY的解释:“避免知识的重复(而不是文本的重复)”。和“少键入,甚至不重复文本。”,第一个是原理的真正含义,但是第二个是常见的误解,因为许多人无法理解过于复杂的废话,例如“每条知识都必须具有唯一,明确,系统内的权威表示”。第二个是不惜一切代价的紧凑性,在这种情况下会破坏,因为它会降低可读性。在有界上下文之间复制代码时,第一种解释会被DDD破坏,因为在这种情况下,松散耦合更为重要。


0

优点:

  1. 这很简洁,但可以让您优雅地将更多内容放入一行。
  2. 您有时可以避免使用变量,这有时可能有用。
  3. 它的性能可能更好。

坏处:

  1. 您正在实现收益,实质上是在对象上的方法中添加功能,而这些功能实际上并不是这些方法的一部分。它返回的内容已经纯粹是为了节省一些字节。
  2. 当一条链通往另一条链时,它将隐藏上下文切换。您可以使用getter获得此功能,但上下文切换时非常清楚。
  3. 跨多行链接看起来很丑陋,不能很好地与缩进配合使用,并且可能导致操作员处理混乱(尤其是在使用ASI的语言中)。
  4. 如果要开始返回对链式方法有用的其他内容,则可能会很难解决它或遇到更多问题。
  5. 您正在将控制权转移给通常出于纯粹方便目的通常不会转移到实体的实体,即使在严格类型的语言中,也无法始终检测到由这种错误引起的错误。
  6. 它的性能可能会更差。

一般:

一个好的方法是一般不要使用链接,直到出现情况或特定的模块特别适合它为止。

在某些情况下,链接可能会严重损害可读性,尤其是在第1点和第2点称重时。

在使用时,它可能会被滥用,例如代替其他方法(例如,传递数组)或以奇怪的方式混合方法(parent.setSomething()。getChild()。setSomething()。getParent()。setSomething())。


0

固执己见的答案

链接的最大缺点是,读者可能很难理解每种方法如何影响原始对象(如果有的话)以及每种方法返回的类型。

一些问题:

  • 链中的方法返回一个新对象,还是同一对象被突变?
  • 链中的所有方法都返回相同的类型吗?
  • 如果不是,当链中的类型发生变化时如何指示?
  • 可以安全地丢弃最后一个方法返回的值吗?

在大多数语言中,使用链接确实很难进行调试。即使链中的每个步骤都位于自己的行上(这种方式破坏了链接的目的),也很难检查每个步骤之后返回的值,尤其是对于非变异方法而言。

取决于语言和编译器,编译时间可能会更慢,因为表达式的解析可能要复杂得多。

我相信,与所有内容一样,链接是一个很好的解决方案,在某些情况下可以派上用场。应该谨慎使用它,了解其含义,并将链元素的数量限制为几个。


0

在类型化语言(缺少auto或等效语言)中,这使实现者不必声明中间结果的类型。

import Participant
import Schedule

Participant participant = new Participant()
... snip...
Schedule s = participant.getSchedule(blah)
s.saveTo(filename)

对于较长的链,您可能要处理几种不同的中间类型,则需要声明每个中间类型。

我相信这种方法确实是在Java中开发的,其中a)所有函数调用都是成员函数调用,并且b)需要显式类型。当然,在条款上需要权衡取舍,从而失去一些明确性,但是在某些情况下,某些人认为值得这样做。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.