方法链接的影响

Effects of method chaining

提问人:RobertPitt 提问时间:9/29/2010 最后编辑:RobertPitt 更新时间:9/29/2010 访问量:2266

问:

我知道在PHP中链接的好处,但是假设我们有以下情况

$Mail = new MailClass("mail")
        ->SetFrom("X")
        ->SetTo("X")
        ->SetSubject("X")
        ->AddRecipient("X")
        ->AddRecipient("X")
        ->AddRecipient("X")
        ->AddRecipient("X")
        ->AddRecipient("X")
        ->AddRecipient("X")
        ->Send();

一遍又一遍地返回和重用对象是否存在任何问题,例如速度或未能遵循最佳实践等问题

如果你是 Fluent-Interface 的新手,这也是一本很好的读物:Martin Fowler on Fluent-Interfaces

我完全理解它不必以这种方式编程,可以这样处理:

$Mail = new MailClass("mail");
$Mail->AddRecipien(
    array(/*.....*/)
);
$Mail->SetFrom("X");
$Mail->SetTo("X");
$Mail->SetSubject("X");
$Mail->Send();

但是假设我有一个这样的对象:

$Order = new Order()
         ->With(22,'TAL')
         ->With(38,'HPK')->Skippable()
         ->With(2,'LGV')
         ->Priority();

请注意,这是此类编程的 Pro 的完美示例->With(38,'HPK')->Skippable()

php 对象 fluent-interface 链接

评论

0赞 AutoSponge 9/29/2010
IMO,类应该验证其实例化参数和测试/控制方法的参数。在我的 mailer 类中,is_valid_email 是类内的私有方法。
0赞 Hannes 9/29/2010
“->With(38,'HPK')->Skippable()”情况不一定是问题,例如,在此场景中,您/类可以知道 Skippable 适用于已添加的最后一个 With 。
2赞 RobertPitt 9/29/2010
是的,但可读性因子大于编译此代码块的正常方法。这只是可读性的优点
0赞 ircmaxell 9/29/2010
就我个人而言,我并不关心大多数代码中的流畅接口。它们在库中很有用,但就生产代码而言,我认为它们掩盖了太多正在发生的事情,使其无法阅读。若要了解 Fluent 接口,必须了解调用的每个方法的接口。是否返回对象?还是返回另一个对象?如果你明确地使用它,你马上就知道了(因为你可以看到它被分配了什么)......所以我认为这实际上损害了可读性......->With()Order
1赞 Arc 9/29/2010
顺便说一句,在 PHP 方法中,名称通常以小写字母开头。

答:

5赞 Hannes 9/29/2010 #1

如果您必须验证某些内容,我认为在 AddRecipient 方法本身中验证它更有意义,但性能应该大致相同。而且我不知道使用方法链接的任何一般缺点。

1赞 Arc 9/29/2010 #2

编辑:更新了答案以匹配问题
函数调用比循环慢,即链接,例如,与调用一个取一个数组的方法相比,该方法在循环中处理该方法会稍微降低性能。
addRecipient()addRecipients()

此外,将更复杂的方法链接到 Fluent API 可能需要对与上次调用的方法相关的数据进行额外的簿记,以便下一次调用可以继续处理该数据,因为所有方法都返回构建链的同一对象。 让我们看一下您的示例:

...
->With(22, 'TAL')
->With(38, 'HPK')->Skippable()
->With(2, 'LGV')
...

这需要你记住,这是要应用于的,而不是 .Skippable()(38, 'HPK')(22, 'TAL')

不过,除非您的代码将在循环中非常频繁地调用,或者当您对 Web 服务器有如此多的并发请求以致它接近其极限时,您几乎不会注意到性能损失(重负载网站就是这种情况)。

另一个方面是方法链接模式强制使用异常来发出信号错误(我并不是说这是一件坏事,它只是与经典的“调用和检查函数结果”编码风格不同)。

然而,通常会有一些函数产生它们所属的对象以外的其他值(例如,那些返回对象和访问器状态的函数)。 重要的是,API 的用户可以确定哪些函数是可链接的,哪些不能,而无需在每次遇到新方法时参考文档(例如,指南说所有 mutator 和只有 mutator 都支持链接)。


对原始问题的回答:

[...]我在链接方面遇到的问题是,您无法真正执行额外的验证 [...]

或者,实现一个专用的验证方法,在设置所有属性后调用该方法,并让它返回验证失败的数组(可以是纯字符串或对象,例如名为 ValidationFailure)。

0赞 Geekster 9/29/2010 #3

这是一把双刃剑。

好的一面?这比重新寻址类更干净,尽管它主要只是一个语法更改,但它会加快处理速度。最好是循环这种链,而不是以长格式循环每个调用。

坏的一面?当人们第一次习惯它时,这将导致安全问题。在这方面,要勤奋地净化传入的变量,这样你就不会在那里传递一些你不应该传递的东西。不要让你的课程过于复杂。

  1. 预先验证您的信息。
  2. 对用户进行预授权。

我认为将这些方法链接到一个循环中没有问题,从性能上讲。

2赞 Mark Baker 9/29/2010 #4

您不能直接从类实例化链接:

$Mail = new MailClass("mail") 
            ->SetFrom("X")
            ->SetTo("Y");

您必须先实例化,然后针对实例化的对象进行链接:

$Mail = new MailClass("mail") ;
$Mail->SetFrom("X")
     ->SetTo("Y");

如果在各个 setter 方法中进行验证(应如此),则需要确保在遇到验证错误时引发异常。你不能简单地在出错时返回一个布尔值 false,否则链接将尝试针对布尔值而不是你的类实例调用下一个方法,你会得到。

致命错误:调用成员函数 SetSubject() 在非对象上 C:\xampp\htdocs\oChainTest.php 上线 23

如果抛出异常,则可以在 try ...抓住

$Mail = new MailClass("mail");
try {
    $Mail->SetFrom("X")
        ->SetTo("Y")
        ->SetSubject("Z");
} catch (Exception $e) {
    echo $e->getMessage();
}

但作为警告,这将使实例处于部分更新状态,对于成功验证/执行的方法,不会回滚(除非您自己编写回滚),并且不会调用异常后的方法。

评论

2赞 Arc 9/29/2010
解决方法是使用工厂方法来实例化对象并从那里链接,例如 .在 5.3 之前的 PHP 版本中,缺点是它们没有后期静态绑定,即静态方法将引用定义它们的类。此外,静态方法调用速度很慢。MailClass::create('mail')->setFrom('X')
0赞 Mark Baker 9/29/2010
@Archimedix - 有效点,后期静态绑定将是天赐之物(一旦我可以强制我的库始终针对 5.3+ 运行)
0赞 RobertPitt 9/29/2010
我总是使用带有静态方法的抽象注册表来实例化对象并将它们存储在数组中,因此我的输出是在方法中完成初始化的地方:Registry::Use("Mail")->X()->Y->Z();Registry::Use("Mail")
0赞 Blablaenzo 1/9/2017
您可以直接从 PHP 5.4+ 中的类实例化链接,请参阅:[stackoverflow.com/a/2188690/3200414]