我对这个用例有一个普遍的问题:我有一个类
A
。这个类有一个非抽象方法 doStuffCallback()
可以被重写,但并不是每个子类都需要它。但是:我想确保如果该方法被重写,子类方法必须调用父类方法。
示例:
abstract class A {
private function doStuff() {
$this->doStuffCallback();
}
protected function doStuffCallback() {
// IMPORTANT CODE HERE
}
}
class B extends A {
protected function doStuffCallback() {
parent::doStuffCallback(); // I want to enforce this because the parents method code is important
// ALSO IMPORTANT CODE
}
}
因为重写的方法做了同样的事情,所以为相同的职责定义两个方法以及调用这两个方法的私有帮助方法会非常难看。像这样:
abstract class A {
private function doStuff() {
$this->callDoStuffCallback();
}
private function callDoStuffCallback() {
$this->internalDoStuffCallback();
$this->doStuffCallback();
// This is VERY ugly
}
private function internalDoStuffCallback() {
// IMPORTANT CODE HERE
}
protected function doStuffCallback() {}
}
class B extends A {
protected function doStuffCallback() {
// IMPORTANT CODE
}
}
这实在是又丑又费力。所以我的问题是: PHP 中有没有办法强制重写方法调用父方法?
不。 PHP 中没有这样的语言特性;在大多数子类型“OO”语言中,此限制是不可能的。
相反,程序必须依赖于明确的文档契约;希望通过单元测试来确保一致性。
也可以使用守卫,这样,在某些时候,当使用父类上的方法时,如果“当前状态”无效(例如,这样那样的方法尚未被使用),它可能会抛出异常。还没打电话)。通过使子类需要调用(如文档契约中定义的)一些特殊方法,而不是简单地重写超级方法,也可以使这一点更加明确。然而,这不属于任何类型系统。
虽然可以使用
self::
范围(例如,调用非重写方法,该方法调用重写方法),但这将涉及进一步的魔法(例如,某些堆栈状态)以避免无限递归循环;并且很容易不小心忽略用法。
我的建议是调用一个(私有)方法,该方法调用与应用的任何逻辑相关的“可能重写”方法,如示例所示(尽管希望有更多特定于任务的驯服)。那么(受保护的)重写方法不需要或不需要处理任何特殊逻辑本身;它也不意味着在父类建立的上下文之外直接调用 - 它只是它当前所声称的,一个特殊的回调。
abstract class A {
private function doStuff() {
$this->doStuffCallback();
}
final protected function doStuffCallback() {
// IMPORTANT CODE HERE
$this->callNewFunction();
}
abstract protected function callNewFunction();
}
class B extends A {
protected function callNewFunction() {
// ALSO IMPORTANT CODE
}
}
所以基本上我会将您希望为每个子项强制执行代码的函数标记为“最终”函数,然后调用一个新的“抽象”函数来强制子项实现它。如果您不想强制使用新的“抽象”功能,只需不要使其抽象即可。
编辑:这基本上是@Fabian Schmengler 的答案,但用你的例子更具体。
abstract class Authorized_Access
{
private bool $is_authorized = false;
/**
* All child classes are forced to call parent::__construct() during it's lifetime
*
*/
public function __construct($something_needed_for_authorization)
{
$this->authorize($something_needed_for_authorization);
}
/**
* Our Authorization method
*/
private function authorize($args)
{
// DO WHAT YOU NEED HERE...
// IF AUTHORIZED
$this->is_authorized = true;
}
/**
* Throw fatal error if the object wasn't authorized until now
*/
public final function __destruct()
{
if ( ! $this->is_authorized ) {
trigger_error("Access not Authorized!", E_USER_ERROR);
}
}
}
子类可以是:
class Admin_Access extends Authorized_Access
{
public function __construct($args)
{
$something_needed_for_authorization = $args['whatever-you-need'];
parent::__construct($something_needed_for_authorization);
// SOME MORE CODE...
}
// AND MAYBE SOME OTHER METHODS...
}
所以基本上,在 php 执行结束时,所有对象都会被破坏(如果不是更早的话)。如果子对象在此之前没有调用父方法,父类 Authorized_Access 将抛出 Fatal Error。
所以我们可以说“子类被迫调用父类的方法”。
您还可以将authorize()方法设为公共/受保护,并直接从子对象而不是父对象的构造函数调用它(但对我来说,调用构造函数更有意义)。想法保持不变...