方法链 - 为什么它是一个很好的做法,还是不行?方法链 - 为什么它是一个很好的做法,还是不行?(M

2019-06-17 10:00发布

方法链接是对象方法返回对象本身,以使结果的做法被称为为另一种方法。 像这样:

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的结果,而那些之前指参与者。 也许这是不好的做法,实际上是写链在环境的变化(是吗?),但即使如此,你必须不断地检查点链,类似于是否实际上保持在同一范围内,或仅在结果工作。

对我来说,似乎是,虽然方法链表面上不会产生可读的代码,重载点符号的意义只会导致更多的混乱。 因为我不认为自己是一个编程高手,我认为是我的错。 所以:我在想什么? 我理解的方法链接不知何故错了吗? 是否有某些情况下,方法链接是特别好,还是有些地方是特别坏?

旁注:我理解这个问题可以理解为掩盖作为一个问题提出意见陈述。 然而,它是不是 - 我真正想知道为什么链接被认为是很好的做法,和我在哪里去错以为它打破了固有的面向对象的符号。

Answer 1:

我同意这是主观的。 在大多数情况下我会避免方法链,但最近我也发现它只是正确的事情的情况下 - 我有它接受像10个参数,并且需要更多的方法,但在大多数时候,你只需要指定一个少数。 随着覆盖此变得非常繁琐的速度非常快。 相反,我选择了链接方法:

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

这有点像一个工厂模式。 该方法链接方法是可选的,但它使编写代码更容易(尤其是智能感知)。 你要知道这是一个孤立的情况下,虽然,是不是在我的代码一般的做法这一点。

问题的关键是 - 在99%的情况下,你也许可以做的一样好或没有方法链,甚至更好。 但有1%的地方,这是最好的办法。



Answer 2:

只是我的2美分;

方法链使调试棘手: - 你想让它你不能把断点在一个简洁点,因此您可以暂停节目正好 - 如果这些方法之一抛出异常,你会得到一个行号,你不知道该方法中的“链”造成的问题。

我认为这是比较好的做法是总是写得很短而简练的线条。 每一行应该只是做一个方法调用。 喜欢更多的线较长的线路。

编辑:评论提到方法链和断行是分开的。 那是真实的。 根据调试器,虽然,它可能会或可能无法放置一个破发点在一份声明中的中间。 即使可以,使用单独的线,中间变量为您提供了更多的灵活性和一大堆,你可以在监视窗口,帮助调试过程检验值。



Answer 3:

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

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

我不使用它时,一个或更多的链接方法将返回比富以外的任何物体在我的例子。 虽然语法就可以,只要你使用了正确的API链中的那个对象,改变对象恕我直言,使事情的可读性和可真混乱,如果针对不同对象的API有任何相似之处链东西。 如果你做一些非常常见的方法调用末( .toString() .print()等等),其对象是你最终取决于演戏? 有人随便读取的代码可能无法赶上,这将是在链而不是原来的基准隐式返回的对象。

链接不同的对象还可能会导致意外空的错误。 在我的例子,假设foo是有效的,所有的方法调用是“安全的”(例如,有效期为富)。 在OP的例子:

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

...有没有保证(如外部开发者在看代码)getSchedule实际上将返回一个有效的,非空的时间表对象。 此外,调试的代码这种风格往往要困难得多,因为许多IDE不会评价在调试时的方法调用作为一个对象,你可以检查。 海事组织,任何时候你可能需要一个对象来检查调试的目的,我更喜欢有它在一个明确的变量。



Answer 4:

Martin Fowler的在这里有一个很好的讨论:

方法链接

何时使用

方法链接可以添加一个很大的一个内部DSL的可读性,因此已成为一些头脑内部DSL几乎synonum。 方法链是最好的,但是,当它与其他功能组合一起用到了。

方法链接是一个像父语法特别有效:: =(这|那)*。 使用不同的方法提供眼看这说法是下一个即将到来的易读的方式。 同样可选参数可以很容易地与方法链跳过。 强制性条款,如父:: =第一第二列表不与基本形式工作,以及,尽管它可以通过使用渐进接口很好地支持。 大多数时候,我宁愿嵌套函数,该函数的情况。

对于方法链接的最大问题是整理问题。 虽然有解决方法,通常,如果你遇到了这个你最好USNG一个嵌套函数。 嵌套函数也是一个更好的选择,如果你正在与上下文变量一塌糊涂。



Answer 5:

在我看来,方法链接是有点新奇。 当然,它看起来很酷,但我没有看到任何真正的优势。

怎么:

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


Answer 6:

这是危险的,因为你可以根据于预期,像那么你的调用返回另一个类的实例多个对象:

我举一个例子:

foodStore是由你拥有许多食品店的对象。 foodstore.getLocalStore()返回保存在最近的商店的参数信息中的对象。 getPriceforProduct(任何)是对象的一个​​方法。

所以,当你调用foodStore.getLocalStore(参数).getPriceforProduct(任何)

你只在FoodStore不依赖你,虽然,但也LocalStore。

如若getPriceforProduct(什么)都没有改变,需要改变的不仅仅是FoodStore而且还称为链式方法的类。

你应该总是瞄准类之间的松散耦合。

话虽这么说,我个人比较喜欢的Ruby编程的时候把它们连。



Answer 7:

这似乎有点主观的。

方法链接是不是soemthing是天生坏或好海事组织。

可读性是最重要的事情。

(还要考虑到有大量的链接将会使事情变得非常脆弱,如果有新的变化的方法)



Answer 8:

许多使用方法链接是为了方便,而不是记住任何可读性关注的一种形式。 方法链是可以接受的,如果它涉及到执行相同的对象相同的动作 - 但只有当它实际上增强可读性,而不是只为写更少的代码。

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



Answer 9:

链接的好处
即,在这里我想用它

我没有看到提到链的一个好处是可变的启动过程中使用它的能力,或通过新对象的方法时,不知道这是不好的做法与否。

我知道这是人为的例子,但是说你有下面的类

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

VS

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. 当它使代码更简单使用


Answer 10:

方法链可以允许设计先进的DSL在Java中直接。 从本质上讲,你可以模拟至少这些类型的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();
}

有了这些简单的规则,就可以实现复杂的DSL的如SQL直接在Java中,如通过做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中cerating 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 );


Answer 11:

方法链可能仅仅是在大多数情况下是新鲜事,但我认为它有它的地方。 一个例子可以中找到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);

这真的是主观的; 每个人都有自己的看法。



Answer 12:

我认为主要的谬论认为这是一般的面向对象的方法时,实际上它是比什么都重要功能的编程方法。

主要的原因,我使用它是专为可读性和防止我的代码通过变量被淹没。

我真的不明白别人在谈论时,他们说这损害可读性。 这是我使用的编程的最简洁和凝聚力的形式之一。

另外这个:

convertTextToVoice.LoadText( “的Source.txt”)ConvertToVoice( “destination.wav”);

是我通常会使用它。 用它来的参数链x个并不怎么我通常使用它。 如果我想将其在参数x个在方法调用中我会使用PARAMS语法:

公共无效FOO(params对象[]项)

投下基于类型的对象或只使用取决于你的使用情况数据类型数组或集合。



Answer 13:

我同意,我为此改变了流畅的界面在我的图书馆实现方式。

之前:

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

后:

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

在“之前”执行功能修改的对象,并在截至return this 。 我改变了执行返回相同类型的新对象

我对这种变化的推理

  1. 返回值无关,与功能,这纯粹是有支持链接部分,它应该根据OOP是一个无效的功能。

  2. 在系统库链接的方法也实现它的方式(如LINQ或字符串):

     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实现是错误的。



Answer 14:

这里完全错过了点,就是方法链接允许 。 这是一个有效的替身“同向”(在某些语言执行不力)。

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

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

这一点很重要的原因一样干总是重要的; 如果A被证明是一个错误,而这些操作都需要基于B进行,您只需要在1米的地方,不是3更新。

从务实角度来说,优点是在这种情况下小。 尽管如此,少打字,痘痘更稳健(DRY),我会接受它。



Answer 15:

我一般恨方法链接,因为我觉得它恶化可读性。 紧凑经常混淆的可读性,但他们是不一样的条款。 如果你在一个声明中所做的一切那么紧凑,但是它的可读性(难以追随)大部分时间不是在多条语句做的。 当你注意到了,除非你不能保证的使用的方法的返回值是相同的,那么方法链将是混乱的根源。

1.)

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

VS

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

2.)

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

VS

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

3.)

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

VS

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

正如你可以看到你赢了几乎为零,因为你必须换行添加到您的单个语句,以使其更具可读性,你必须增加缩进说清楚,你说的是不同的对象。 那么,如果我想使用基于identation语言,那么我会学习,而不是这样做,更何况大多数的IDE将自动格式化代码中删除缩进Python的。

我认为唯一的地方,这样的链接可能是有用的是管道流在CLI或SQL连接多个查询一起。 两者都有多个语句的价格。 但是,如果你要解决复杂的问题,你会与那些付出代价,写中的变量或写的bash脚本和存储过程或视图多个语句代码甚至结束。

因为干燥的解释:“避免知识的重复(不是文本的重复)。” 和“种类少,甚至不重复的文本。”,第一个什么原则的真正含义,但第二个是常见的误区,因为很多人无法理解过于复杂的废话,如“每一块的知识必须有一个单一的,明确的,系统内的权威表示”。 第二个是不惜一切代价,打破在这种情况下紧凑,因为它恶化可读性。 通过DDD第一种解释休息,当你复制界上下文之间的代码,因为松耦合是在那种情况下更重要。



Answer 16:

好:

  1. 它的简洁,又可以让你把更多的成一行典雅。
  2. 有时你能避免使用一个变量,它有时可能会是有用的。
  3. 它可能会表现得更好。

坏:

  1. 你执行回报,主要增加功能在对象上的方法,是不是真的有什么这些方法是为了做一个组成部分。 它返回你已经有纯粹是为了节省几个字节的东西。
  2. 它隐藏上下文切换,当一个链导致另一个。 你可以用干将得到这个,但它是在上下文切换非常清楚。
  3. 链接多行相貌丑陋,不与缩进发挥出色,可能会导致一些运营商处理混乱(特别是在语言与ASI)。
  4. 如果你想开始返回别的,就是一个链接的方法是有用的,你可能将有一个更难的时间来修复,或打更多的问题吧。
  5. 你卸载控件,你通常不会卸载到纯粹为了方便实体,甚至造成这个不能总是被检测严格类型的语言错误。
  6. 它可能表现更差。

一般:

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

链接1点和2称重尤其是在某些情况下相当严重伤害可读性。

上accasation它可能被误用,如,而不是另一种方法(传递数组例如)或混合方法在奇异方式(parent.setSomething()。getChild()。setSomething()。的getParent()。setSomething())。



文章来源: Method chaining - why is it a good practice, or not?