延迟静态绑定(late static binding)
作用:父类可以使用子类重载的静态方法
例子:
<?php
class A{
public static function who(){
echo __CLASS__;
}
public static function staticTest(){
static::who();
}
public static function selfTest(){
self::who();
}
}
class B extends A{
public static function who(){
echo __CLASS__;
}
}
B::staticTest();//B
B::selfTest();//A
?>
static与self的区别:
- self是类内指针,指向本类的静态方法和属性
- static使得父类能访问子类的重载静态方法
但是这么做又有什么意义呢?先看一下下面的代码:
<?php
class base{
}
class aClass extends base{
public static function create(){
return new aClass();
}
}
class bClass extends base{
public static function create(){
return new bClass();
}
}
var_dump(aClass::create());//object(aClass)#1 (0) { }
var_dump(bClass::create());//object(bClass)#1 (0) { }
?>
遵循面向对象的思想,子类重复的代码应该放在父类中实现,于是改进后:
<?php
class base{
public static function create(){
return new self();
}
}
class aClass extends base{
}
class bClass extends base{
}
var_dump(aClass::create());
var_dump(bClass::create());
?>
看起来很完美,运行一下:
object(base)#1 (0) { }
object(base)#1 (0) { }
现实总是这么残酷,原来父类中的self被解析为base这个父类,而不是解析成它的子类。
为了解决这个问题,php5.3引入了延迟静态绑定
改进为延迟静态绑定:
<?php
class base{
public static function create(){
return new static();
}
}
class aClass extends base{
}
class bClass extends base{
}
var_dump(aClass::create());//object(aClass)#1 (0) { }
var_dump(bClass::create());//object(bClass)#1 (0) { }
?>
在php手册里看到一个很有趣的例子
<?php
class A {
public static function foo() {
static::who();
}
public static function who() {
echo __CLASS__."\n";
}
}
class B extends A {
public static function test() {
A::foo();
parent::foo();
self::foo();
}
public static function who() {
echo __CLASS__."\n";
}
}
class C extends B {
public static function who() {
echo __CLASS__."\n";
}
}
C::test();
?>
以上会输出:
A
C
C
A::foo()得到的结果是毫无疑问的,但是parent::foo()和self::foo()得到的结果就有点匪夷所思了。
而官方也只给出一句解释而已:
后期静态绑定的解析会一直到取得一个完全解析了的静态调用为止。另一方面,如果静态调用使用 parent:: 或者 self:: 将转发调用信息。
看起来不知所云,于是我做了一些测试来理解:
首先,parent:: 和 self::各自解析为什么?
我把test()方法改了一下,让他们去调用自己的who()方法:
class B extends A {
public static function test() {
parent::who();//result:A
self::who();//result:B
}
}
parent::和self::是属于静态绑定,在编译期就确定了解析对象是谁。如self::就解析为自身B,而parent::就解析为自身的父类A。
而static::可以理解为是一种动态绑定,即在编译期无法确定解析对象是谁,只有到运行期才计算出解析的对象是谁。
按照以上的说法,A::foo()和B::foo()也不能得到C的结果?
这就得重新翻翻手册:
1.后期静态绑定本想通过引入一个新的关键字表示运行时最初调用的类来绕过限制。最终决定不引入新的关键字,而是使用已经预留的 static 关键字。
2.如果静态调用使用 parent:: 或者 self:: 将转发调用信息。
也就是说静态调用使用parent::或者self::时,他们会附带一个信息来告诉static::说,运行时最初调用的类是C,这也就是转发调用(转发最初调用者的信息)
验证方法:可以用get_called_class()来检查最初调用的类是谁?
修改后代码如下:
<?php
class A {
public static function foo() {
static::who();
}
public static function who() {
var_dump(get_called_class());
}
}
class B extends A {
public static function test() {
A::foo();
parent::foo();
self::foo();
}
public static function who() {
var_dump(get_called_class());
}
}
class C extends B {
public static function who() {
var_dump(get_called_class());
}
}
C::test();
?>
运行结果:
string(1) "A"
string(1) "C"
string(1) "C"
在这里还有一个疑问:如果C类里没有who()方法,那static::会解析成谁?
<?php
class A {
public static function foo() {
static::who();
}
public static function who() {
echo __CLASS__;
}
}
class B extends A {
public static function test() {
A::foo();
parent::foo();
self::foo();
}
public static function who() {
echo __CLASS__;
}
}
class C extends B {
}
C::test();
?>
运行结果:
A
B
B
我们看看原先的官方解释:
后期静态绑定的解析会一直到取得一个完全解析了的静态调用为止。
也就是说,虽然运行期static::得到最初调用的类来自C,但是C中并没有who()方法,于是就去找C的父类B,B有who()方法,所以static::就解析为B
参考资料:
理解Java中的静态绑定和动态绑定