后期静态绑定
本课属于 Coddy PHP 学习旅程的“面向对象编程”单元。第 36 课,共 91 课。
在父类中使用 self:: 时,它始终指向编写它的类,而不是实际使用的类。这可能会导致继承中的行为出乎意料。延迟静态绑定通过 static:: 关键字解决了这个问题。
请看这个示例,其中 self:: 的行为与预期不符:
<?php
class ParentClass {
public static function create() {
return new self();
}
public static function getClassName() {
return self::class;
}
}
class ChildClass extends ParentClass {}
$obj = ChildClass::create();
echo get_class($obj) . "\n";
echo ChildClass::getClassName();
输出:
ParentClass
ParentClass尽管我们调用了 ChildClass::create(),但得到的是一个 ParentClass 对象,因为 self:: 会在编译时解析。现在我们改用 static:::
<?php
class ParentClass {
public static function create() {
return new static();
}
public static function getClassName() {
return static::class;
}
}
class ChildClass extends ParentClass {}
$obj = ChildClass::create();
echo get_class($obj) . "\n";
echo ChildClass::getClassName();
输出:
ChildClass
ChildClass使用 static:: 时,PHP 会等到运行时才确定要使用哪个类。这称为“延迟”绑定,因为这个决定会推迟到运行时。这对于工厂方法以及任何应遵循继承关系的静态方法都至关重要。
关键点:如果你希望子类正确继承并自定义静态行为,请使用 static::,而不是 self::。
挑战
简单我们来构建一个模型注册系统,展示后期静态绑定的强大之处。你将创建一个基础 Model 类,其中包含一个子类可以继承的工厂方法。借助 static::,每个子类都能正确创建自身的实例,而不是父类的实例。
你将把代码分成 4 个文件:
Model.php:创建一个基础Model类,其中包含受保护的静态属性$tableName,并将它设为"models"。添加一个静态方法create(),使用new static()返回调用该方法的类的新实例。再添加一个静态方法getTable(),让它返回static::$tableName。最后,添加一个describe()方法,使用static::class和static::$tableName返回"[ClassName] from table [tableName]"。User.php:创建一个继承Model的User类。引入父类文件。将静态属性$tableName覆盖为"users"。该类继承Model的所有方法,无需做任何修改。Product.php:创建一个继承Model的Product类。引入父类文件。将静态属性$tableName覆盖为"products"。同样,该类会继承父类的所有方法。main.php:引入User和Product文件。使用继承而来的create()工厂方法,分别创建User和Product的实例。对每个实例调用describe(),并将结果各自输出到一行。然后使用User::getTable()和Product::getTable()输出每个类的表名,每个表名各占一行。
这里的关键在于,create() 和 getTable() 方法只在父类 Model 中定义,但由于 static:: 会将绑定延迟到运行时,它们仍能正确用于子类。调用 User::create() 时,PHP 创建的是 User 实例,而不是 Model 实例,因为 static:: 指向实际被调用的类。
输出应包含 4 行:User 实例的描述、Product 实例的描述、User 的表名,以及 Product 的表名。
亲手试试
<?php
require_once 'User.php';
require_once 'Product.php';
// TODO: 使用继承来的 create() 工厂方法创建一个 User 实例
// TODO: 使用继承来的 create() 工厂方法创建一个 Product 实例
// TODO: 调用 User 实例的 describe() 方法并打印结果
// TODO: 调用 Product 实例的 describe() 方法并打印结果
// TODO: 打印 User::getTable()
// TODO: 打印 Product::getTable()
?>这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: PHP 在线编译器