父类方法返回子类实例:PHP延迟静态绑定
案例分析
先前的PHP项目中,看到类似于以下的一段代码:
<?php class DBHandler { public function get() { } } class MySQLHandler extends DBHandler { // 这里一个create public static function create() { echo "MySQL"; return new self(); } public function get() { echo "MySQL get()"; } } class MemcachedHandler extends DBHandler { // 这里又有一个create public static function create() { echo "Memcached"; return new self(); } public function get() { echo "Memcached get"; } } function get(DBHandler $handler) { $handler->get(); } $dbHandler = MySQLHandler::create(); get($dbHandler);
可以看到,在 MySQLHandler 和 MemcachedHandler 类中,都有一个 create函数,除掉我的输出语句,发现它们一模一样,这就是代码冗余。是的,需要进行代码重构。
进行简单重构
对上面的代码进行重构,如下:
<?php class DBHandler { public static function create() { echo "create"; return new self(); } public function get() { } } class MySQLHandler extends DBHandler { public function get() { echo "MySQL get()"; } } class MemcachedHandler extends DBHandler { public function get() { echo "Memcached get"; } } function get(DBHandler $handler) { $handler->get(); } $dbHandler = MySQLHandler::create(); get($dbHandler);
将create函数移到DBHandler类中,看起来还不错,至少少了一坨那糟糕的代码。
貌似是错的
运行一下,却发现,并没有打印出我们期望的MySQL get()。什么情况?这说明,并没有调用MySQLHandler的get函数,但是代码明明调用了啊,这说明,new self()这句代码有问题。这有什么问题?这就需要说到今天总结的重点了————延迟静态绑定。
延迟静态绑定
在PHP5.3以后引入了延迟静态绑定。看下面这段代码:
<?php class A { public static function who() { echo __CLASS__; } public static function test() { self::who(); } } class B extends A { public static function who() { echo __CLASS__; } } B::test();
上面的代码输出了A,但是我希望它输出B,这就是问题的所在。这也是self和__CLASS__的限制。使用self::或者 __CLASS__对当前类的静态引用,取决于定义当前方法所在的类。所以,这就很好的解释了为什么上面的代码输出了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();
后期静态绑定本想通过引入一个新的关键字表示运行时最初调用的类来绕过限制。简单地说,这个关键字能够让你在上述例子中调用 test() 时引用的类是 B 而不是 A。最终决定不引入新的关键字,而是使用已经预留的 static 关键字。
这就是后期静态绑定的根本————static关键字的另类用法。对于文章一开始的例子,可以这么改:
return new static(); // 改变这里,后期静态绑定
这种使用后期静态绑定,在使用PHP实现设计模式的时候,你会感到很轻松的。
延伸阅读:
PHP的继承方法如何获取子类名?get_class() 和 get_called_class()
解决 PhpStorm 对 用单例模式实例化PHP类时,代码自动提示功能失效 的问题
参考:
http://my.oschina.net/liuhui1990/blog/38611?fromerr=1ZoOQLOl