关于oop:为什么PHP 5.2+不允许使用抽象静态类方法?

Why does PHP 5.2+ disallow abstract static class methods?

在php 5.2中启用了严格警告之后,我看到了一个项目中的严格标准警告,这个项目最初是在没有严格警告的情况下编写的:

Strict Standards: Static function Program::getSelectSQL() should not be abstract in Program.class.inc

所讨论的函数属于抽象父类程序,并声明为抽象静态函数,因为它应在子类(如tvprogram)中实现。

我确实在这里找到了这个变化的参考文献:

Dropped abstract static class functions. Due to an oversight, PHP 5.0.x and 5.1.x allowed abstract static functions in classes. As of PHP 5.2.x, only interfaces can have them.

我的问题是:有人能清楚地解释为什么PHP中不应该有抽象的静态函数吗?


静态方法属于声明它们的类。扩展类时,可以创建同名的静态方法,但实际上并没有实现静态抽象方法。

用静态方法扩展任何类也一样。如果扩展该类并创建具有相同签名的静态方法,则实际上不会重写超类的静态方法。

编辑(2009年9月16日)对此进行更新。运行php 5.3时,我看到抽象静态返回,无论好坏。(有关详细信息,请参阅http://php.net/lsb)

更正(由Philfreo)php 5.3中仍然不允许使用abstract static,lsb是相关的,但不同。


这是一个漫长而悲伤的故事。好的。

当PHP5.2首次引入此警告时,后期的静态绑定还没有使用该语言。如果您不熟悉最新的静态绑定,请注意,类似这样的代码并不像您期望的那样工作:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

abstract class ParentClass {
    static function foo() {
        echo"I'm gonna do bar()";
        self::bar();
    }

    abstract static function bar();
}

class ChildClass extends ParentClass {
    static function bar() {
        echo"Hello, World!";
    }
}

ChildClass::foo();

撇开严格的模式警告不谈,上面的代码不起作用。foo()中的self::bar()调用显式地引用ParentClassbar()方法,即使将foo()作为ChildClass的方法调用。如果尝试在关闭严格模式的情况下运行此代码,您将看到"php致命错误:无法调用抽象方法parentClass::bar()"。好的。

鉴于此,PHP5.2中的抽象静态方法是无用的。使用抽象方法的整个要点是,您可以编写调用该方法的代码,而不知道它将调用什么实现,然后在不同的子类上提供不同的实现。但是,由于php 5.2没有提供一种干净的方法来编写一个父类的方法,该方法调用子类的静态方法,因此抽象静态方法的这种用法是不可能的。因此,在php 5.2中使用abstract static是错误的代码,可能是由于对self关键字如何工作的误解。对此提出警告是完全合理的。好的。

但是后来又增加了php 5.3的功能,可以引用通过static关键字调用方法的类(与self关键字不同,后者总是引用定义方法的类)。在上面的示例中,如果您将self::bar()更改为static::bar(),它在php 5.3及更高版本中可以正常工作。你可以在新的self和新的static上阅读更多关于selfstatic的内容。好的。

添加了static关键字后,让abstract static抛出警告的明确理由就消失了。后期静态绑定的主要目的是允许父类中定义的方法调用将在子类中定义的静态方法;考虑到后期静态绑定的存在,允许抽象静态方法似乎合理且一致。好的。

我想,你还是可以提出保留警告的理由。例如,您可能会争辩说,由于PHP允许您调用抽象类的静态方法,在我上面的示例中(甚至在用static替换self之后),您公开了一个公共方法ParentClass::foo(),它已被破坏,并且您实际上不想公开。使用一个非静态类——也就是说,使所有方法的实例方法和ParentClass的子类都是单例的或者其他的——可以解决这个问题,因为ParentClass是抽象的,不能实例化,因此不能调用它的实例方法。我认为这个论点很弱(因为我认为公开ParentClass::foo()不是什么大问题,使用单例类代替静态类通常是不必要的冗长和丑陋),但你可能会合理地反对——这是一个有点主观的要求。好的。

所以基于这个论点,php开发人员用语言保存了警告,对吗?好的。

呃,不完全是。好的。

上面链接的php bug报告53081要求删除警告,因为添加了static::foo()构造使抽象静态方法变得合理和有用。RasmusLerdorf(PHP的创建者)首先将请求标记为伪请求,然后通过一长串错误的推理来证明警告的合理性。最后,这种交换发生在:好的。

Giorgio

i know, but:

Ok.

1
2
3
4
5
6
7
8
9
10
11
12
13
abstract class cA
{
      //static function A(){self::B();} error, undefined method
      static function A(){static::B();} // good
      abstract static function B();
}

class cB extends cA
{
    static function B(){echo"ok";}
}

cB::A();

Rasmus

Right, that is exactly how it should work.

Ok.

Giorgio

but it is not allowed :(

Ok.

Rasmus

What's not allowed?

Ok.

1
2
3
4
5
6
7
8
9
10
abstract class cA {
      static function A(){static::B();}
      abstract static function B();
}

class cB extends cA {
    static function B(){echo"ok";}
}

cB::A();

This works fine. You obviously can't call self::B(), but static::B()
is fine.

Ok.

Rasmus声称他的示例中的代码"工作正常"是错误的;正如您所知,它抛出了一个严格的模式警告。我猜他在测试时没有打开严格的模式。不管怎样,一个困惑的拉斯穆斯错误地将请求关闭为"伪造"。好的。

这就是为什么警告仍然是用语言表达的。这可能不是一个完全令人满意的解释——你可能来这里是希望有一个合理的理由警告。不幸的是,在现实世界中,有时选择是从平凡的错误和错误的推理中产生的,而不是从理性的决策中产生的。这只是其中之一。好的。

幸运的是,作为php-rfc的一部分,可评估的nikita popov已经从php 7语言中删除了警告:重新分类e_-strict notices。最终,理智占了上风,一旦php 7发布,我们都可以愉快地使用abstract static,而不会收到这种愚蠢的警告。好的。好啊。


对于这个问题,有一个非常简单的解决方法,从设计的角度来看,这实际上是有意义的。正如乔纳森所写:

Same goes for extending any class with static methods. If you extend that class and create a static method of the same signature, you are not actually overriding the superclass's static method

所以,作为一项工作,你可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
abstract class MyFoo implements iMyFoo {

    public static final function factory($type, $someData) {
        // don't forget checking and do whatever else you would
        // like to do inside a factory method
        $class = get_called_class()."_".$type;
        $inst = $class::getInstance($someData);
        return $inst;
    }
}


interface iMyFoo {
    static function factory($type, $someData);
    static function getInstance();
    function getSomeData();
}
?>

现在,您可以强制任何类(子类化myfoo)实现getInstance静态方法和公共getSomeData方法。如果不将myfoo子类化,您仍然可以实现imyfoo来创建具有类似功能的类。


我知道这是旧的,但是……

为什么不直接抛出该父类的静态方法的异常,这样如果不重写它,就会导致异常。


我认为抽象类/接口可以看作是程序员之间的契约。它更多地处理事物的外观/行为,而不是实现实际的功能。正如在php5.0和5.1.x中所看到的,这不是阻止PHP开发人员这样做的自然法则,而是促使其他语言中的OO设计模式一起使用的冲动。基本上,如果一个人已经熟悉其他语言,这些想法试图防止意外的行为。


我认为没有任何理由禁止静态抽象函数。没有理由禁止它们的最好的理由是,它们被允许在Java中使用。问题是:-技术上可行吗?-是的,因为存在于PHP 5.2中,它们存在于Java中。所以我能做到。我们应该这样做吗?-它们有意义吗?对。实现类的一部分并将类的另一部分留给用户是有意义的。它在非静态函数中是有意义的,为什么它不应该对静态函数有意义呢?静态函数的一种用法是类,其中不能有多个实例(单例)。例如,加密引擎。它不需要存在于多个实例中,并且有理由防止这种情况发生——例如,您只需要保护内存的一部分以防入侵者。因此,实现引擎的一部分并将加密算法留给用户是完全有意义的。这只是一个例子。如果你习惯使用静态函数,你会发现更多。


在php 5.4+中,使用特性:

1
2
3
4
5
trait StaticExample {
    public static function instance () {
    return new self;
    }
}

在你们班上,把乞讨的话说出来:

1
use StaticExample;


研究PHP的"后期静态绑定"问题。如果您将静态方法放在抽象类上,您可能会很快遇到它,而不是很晚。严格的警告告诉您避免使用中断的语言功能是有意义的。