【问题标题】:Effects of method chaining方法链的影响
【发布时间】:2010-09-29 12:57:35
【问题描述】:

我知道在 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();

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

【问题讨论】:

  • 类应该,IMO,验证它们的实例化参数和测试/控制它们的方法的参数。在我的邮件程序类中,is_valid_email 是类中的私有方法。
  • "->With(38,'HPK')->Skippable()" 情况不一定是问题,例如在这个场景中,您/班级可以知道 Skippable 适用于最后加上那个。
  • 是的,但可读性因素大于编译此代码块的常规方法。这只是为了提高可读性
  • 就个人而言,我不关心大多数代码中的流畅接口。它们在库中很有用,但就生产代码而言,我认为它们掩盖了太多可读的内容。要理解一个流畅的接口,你必须理解每个被调用方法的接口。 ->With() 是否返回 Order 对象?还是它返回另一个对象?如果您明确使用它,您会立即知道(因为您可以看到它分配给了什么)......所以我认为它实际上会损害可读性......
  • 附带说明,PHP 中的方法名称通常以小写字母开头。

标签: php object fluent-interface chaining


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

    $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...catch 中

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

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

    【讨论】:

    • 解决方法是使用工厂方法从那里实例化对象和链,例如MailClass::create('mail')->setFrom('X')。 5.3 之前的 PHP 版本的缺点是它们没有后期静态绑定,即静态方法将引用定义它们的类。此外,静态方法调用很慢。
    • @Archimedix - 有效点,后期静态绑定将是天赐之物(一旦我可以强制我的库始终针对 5.3+ 运行)
    • 我总是使用带有静态方法的抽象注册表来实例化对象并将它们存储在一个数组中,所以我的结果将是Registry::Use("Mail")->X()->Y->Z();,其中在方法内完成初始化:Registry::Use("Mail")
    • 您可以直接从 PHP 5.4+ 中的类实例化链接查看:[stackoverflow.com/a/2188690/3200414]
    【解决方案3】:

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

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

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

    这要求您记住Skippable() 将应用于(38, 'HPK') 而不是(22, 'TAL')

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

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

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


    回答原问题:

    [...] 链接的问题是你不能真正执行额外的验证 [...]

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

    【讨论】:

      【解决方案4】:

      这是一把双刃剑。

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

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-09-04
        • 2019-02-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-29
        相关资源
        最近更新 更多