14

从 5.3 版开始,PHP 支持静态方法的后期绑定。虽然它无疑是一个有用的特性,但只有少数情况下确实需要使用它(例如 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::在这个例子中使用的原因与上一个类似。之所以使用它,是因为可以通过扩展它并覆盖常量来使此类匹配不同格式的标签。


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

4

3 回答 3

16

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

后期静态绑定的引入修复了 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();
?>
于 2009-11-29T20:23:19.180 回答
3

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

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

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

假设您有一个使用 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”)意味着撒谎不必是一个大规模的手术,这是一个加号,因为测试代码离得越远生产代码,测试结果的意义越小。

于 2009-11-29T20:20:41.483 回答
1

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

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

于 2010-11-24T00:13:03.450 回答