【问题标题】:Is it possible to overuse late static binding in PHP?是否可以在 PHP 中过度使用后期静态绑定?
【发布时间】:2010-12-21 10:54:24
【问题描述】:

从 5.3 版开始,PHP 支持 late binding 用于静态方法。虽然它无疑是一个有用的功能,但只有少数情况下确实需要使用它(例如 Active Record 模式)。

考虑以下示例:

1.便利构造函数 (::create())

class SimpleObject
{
    public function __construct() { /* ... */ }

    public static function create()
    {
        return new static; // or: return new self;
    }
}

如果这个类可以扩展(但是,它没有被同一个包中的任何类扩展),是否应该使用后期静态绑定来使其更容易扩展(无需重写 ::create() 方法,以及更多重要的是,不必记住这样做)?

注意:这个习惯用法是用来解决不能在刚刚构造的对象上调用方法的问题:new SimpleObject()->doStuff() 在 PHP 中是无效的。


2。类常量

class TagMatcher
{
    const TAG_PATTERN = '/\<([a-z\-]+?)\>/i';

    private $subject;

    public function construct($subject) { $this->subject = $subject; }

    public function getAllTags()
    {
        $pattern = static::TAG_PATTERN;
        preg_match_all($pattern, $this->subject);
        return $pattern[1];
    }
}

本例中使用static::的原因与上一个类似。之所以使用它,是因为该类可以通过扩展它并覆盖常量来匹配不同格式的标签。


那么,总结一下,后期静态绑定的这些用途(和类似用途)是不是有点矫枉过正?是否有明显的性能影响?另外,频繁使用后期绑定是否会降低操作码缓存带来的整体性能提升?

【问题讨论】:

  • 嗯,第二个也可以通过覆盖私有变量并使用$this 来实现,对吧?还是我误会了什么?
  • 是的,但我想不出一个更好的例子……将它作为const 意味着避免在所有实例中复制它。即使 PHP 的写时复制会对此进行优化,它作为类常量当然看起来更好,因为它实际上是类范围内的常量值。
  • 你是对的,new SimpleObject()-&gt;doStuff() 不会在你新建的对象上调用doStuff()。但是您不需要后期静态绑定来执行此操作,因为 PHP 确实 支持它。你只需要把它写成(new SimpleObject())-&gt;doStuff()

标签: php late-binding late-static-binding


【解决方案1】:

那么,总结一下,后期静态绑定的这些用途(和类似用途)是不是有点矫枉过正?是否有任何明显的性能影响?另外,频繁使用后期绑定是否会降低操作码缓存带来的整体性能提升?

后期静态绑定的引入修复了 PHP 对象模型中的一个缺陷。这与性能无关,而与语义有关。

例如,只要方法的实现不使用$this,我就喜欢使用静态方法。仅仅因为方法是静态的并不意味着您有时不想覆盖它。在 PHP 5.3 之前,行为是如果您覆盖静态方法,则不会标记错误,但 PHP 会继续并静默使用父版本。例如,下面的代码在 PHP 5.3 之前打印“A”。这是非常出乎意料的行为。

后期静态绑定修复它,现在相同的代码打印“B”。

<?php
class A {
  public static function who() {
    echo __CLASS__;
  }
  public static function test() {
    static::who();
  }
}

class B extends A {
  public static function who() {
    echo __CLASS__;
  }
}

B::test();
?>

【讨论】:

  • 我知道这是一个旧线程,但通过谷歌找到了它。无论 PHP 版本如何,您的示例代码实际上都应该打印“A”。要激活后期静态绑定,请使用 static 关键字。例如。替换 self::who();与静态::谁();它应该打印'B'
  • 我同意 Ewan,如果您不使用 $this,请将其设为静态。
【解决方案2】:

静态方法(早期或后期绑定)会产生紧密耦合并(因此)降低可测试性。您可以在 PHP 中创建大型程序,而无需使用多个静态调用。对我来说,后期静态方法不是一个特性。

编辑回答 Marco Demaio 的问题,静态方法如何降低可测试性?

很抱歉,如果这一切对您来说都是显而易见的,静态成员(数据和方法)很有用,如果负责任地使用不会造成任何伤害,我指的是它们普遍存在的误用。 p>

假设您有一个使用 SQL 数据库的 Web 应用程序。您的业​​务对象可以使用静态接口或通过多态性检索数据。要么

class MyBusinessObject
extends...
{
  public function doThisOrThat(...)
  {
    $results = db::query('sql string...');
    ...
  }
}

class MyBusinessObject
extends...
{
  public function __construct(dbconn $db)
  {
    $this->db = $db;
  }
  private $db;
  public function doThisOrThat(...)
  {
    $results = $this->db->query('sql string...');
    ...
  }
}

后者更容易测试(例如:我想测试从某某输入构造的 sql 字符串是某某某某),因为创建dbconn 接口的另一个实现比创建另一个实现更容易就是改变db::的意思。为什么你想要呢?因为您不需要真正的数据库来测试 sql 组合行为,事实上,在没有真正的数据库的情况下测试 会更容易。此外,如果您的测试涉及 CUT(被测代码)的另一个方面,则更容易删除 sql 使用者。

测试总是意味着对被测试的代码撒谎关于它的合作者,并且放弃静态接口(“双冒号”或“quadridot”)意味着谎言不必是一个大规模的手术,这是一个加号,因为被测试的距离越远代码来自生产代码,测试结果的意义越小。

【讨论】:

  • +1 自从我开始使用实例而不是静态方法后,一切都变得容易了
  • @just someone:当您说静态方法创建紧密耦合降低可测试性时,您是什么意思?我不明白他们如何降低可测试性。
  • @just someone: +1 一个很好的例子,说明为什么静态不适合在测试期间解耦
  • 虽然上述情况仍然正确,但您可以使用 Facades 和 IoC 容器解决该问题。
  • 那个问题是什么?
【解决方案3】:

我发现需要使用后期静态绑定的地方是允许模拟静态方法以使用 PHPUnit 进行单元测试。我的问题是我不喜欢严格更改代码以允许模拟,但我可以克服这个问题。

然而,为了回答您的问题,我敢打赌,无论这带来什么性能成本,与大多数程序运行时相比,它都会相形见绌。换句话说,它不会产生明显的差异。

【讨论】:

    猜你喜欢
    • 2019-01-16
    • 2012-04-21
    • 1970-01-01
    • 2021-12-27
    • 2022-10-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-27
    相关资源
    最近更新 更多