这不是技术上的限制;你要求的内容与面向对象编程原则不符。
你的抽象类是一个合约;让我们定义基类:
class AbstractDeliverer {
abstract public function setTarget(
AbstractDeliveryTarget $deliveryTarget
): Delivery;
}
还有一些交付目标
class AbstractDeliveryTarget {}
class EmailDeliveryTarget extends AbstractDeliveryTarget {}
class SMSDeliveryTarget extends AbstractDeliveryTarget {}
那么你可以在其他地方编写这个:
function deliverAll(AbstractDeliverer $d) {
$e = new EmailDeliveryTarget;
$d->setTarget($e);
$s = new SMSDeliveryTarget;
$d->setTarget($s);
}
因为我们知道$d
是一个AbstractDeliverer
,所以我们知道将$e
和$s
传递给它应该都可以正常工作。这就是当我们确保输入数据属于该类型时向我们保证的契约。
现在让我们看看如果按照您想要的方式进行扩展会发生什么:
class EmailOnlyDeliverer extends AbstractDeliverer {
public function setTarget(
EmailDeliveryTarget $emailDeliveryTarget
): Delivery {
/* ... */
}
}
$emailonly = new EmailOnlyDeliverer;
我们知道 $e instanceOf AbstractDeliverer
将会是 true
,因为我们已经继承了,所以我们知道我们可以安全地调用我们的 deliverAll
方法:
deliverAll($emailonly);
函数的第一部分很好,并且将有效地运行这个:
$e = new EmailDeliveryTarget;
$emailonly->setTarget($e);
但是随后我们遇到了这一部分:
$s = new SMSDeliveryTarget;
$emailonly->setTarget($s);
哎呀!致命错误!但是在AbstractDeliverer
合约中告诉我们这是正确的值要传递!出了什么问题?
规则是子类必须接受父类接受的所有输入,但如果它想要,它可以接受附加输入,这被称为“逆变性”。(相反,返回类型是“协变的”:子类绝不能返回父类无法返回的值,但可以做出更强的承诺,只返回这些值的子集;我会让您自己想出一个例子。)