声明与接口不兼容 (PHP)

假设我有一个父类和两个这样的子类


abstract class Vehicle {

    public function setManufacturer(string $manufacturer) { ... }

}


class Bicycle extends Vehicle {

    public function addSaddle() { ... }

}


class Car extends Vehicle {

    public function setSpeedLimit(float $limit) { ... }

}

现在我想通过这样的接口使用这些对象


interface VehicleInterface {

    public function create(Vehicle $vehicle);

}


class BicycleService implements VehicleInterface {

    public function create(Bicycle $bicycle) {

        $bicycle->setManufacturer('Some company'); // Common for all Vehicle objects

        $bicycle->addSaddle(); // Common only for Bicycle objects

    }

}

看来这是不可能的,因为BicycleService create(...)与接口不兼容。除了删除类型提示之外,还有什么办法可以解决这个问题吗?


九州编程
浏览 128回答 4
4回答

紫衣仙女

当你在班级中实施时,你必须遵守合同。因此,无论对实现进行分类,该类都Vehicle 必须具有setManufacturer您声明的方法。

守候你守候我

考虑一下:class NotBicycle implements Vehicle { ... }interface VehicleInterface {    public function create(Vehicle $vehicle);}class BicycleService implements VehicleInterface {    public function create(Bicycle $bicycle) {        $bicycle->setManufacturer('Some company'); // Common for all Vehicle objects        $bicycle->addSaddle(); // Common only for Bicycle objects    }}function createVehicle(VehicleInterface $service, Vehicle $vehicle) {    $service->create($vehicle);}$service = new BicycleService();$vehicle = new NotBicycle();createVehicle($service, $vehicle);即使你以某种方式只能接受Bicycle其中的一个BicycleService,createVehicle($service, $vehicle)仍然会起作用,因为VehicleInterface有一个方法create(Vehicle $vehicle)。因此,为了让它按照您想要的方式工作,您需要从根本上打破界面的本质。您唯一真正的选择是添加运行时类型检查。就像例如class BicycleService implements VehicleInterface {    public function create(Vehicle $bicycle) {        if (!$bicycle instance of Bicycle) {            throw new TypeError('Expected Bicycle but got '.get_class($bicycle));         }        $bicycle->setManufacturer('Some company'); // Common for all Vehicle objects        $bicycle->addSaddle(); // Common only for Bicycle objects    }}您尝试做的事情称为协变方法参数类型,在大多数面向对象的编程语言中是不允许的,因为它违反了里氏替换原则

30秒到达战场

Bicycle您需要删除带有 的类型Vehicle。你可能需要检查if ($bicycle instanceof Bicycle)抽象层次太抽象了。

慕尼黑的夜晚无繁华

您需要将您的Vehicle类传递给它,然后用于instanceof检查接口。在下面的例子中,我更改了命名约定。interface Vehicle {    public function setSpeedLimit(Float $limit) : Car;    public function setManufacturer(String $manufacturer) : MethodOfTransport;}interface NonVehicle {    public function addSaddle() : Bicycle;    public function setManufacturer(String $manufacturer) : MethodOfTransport;}abstract class MethodOfTransport{    public function setManufacturer(String $manufacturer) : MethodOfTransport { return $this; }}class Bicycle extends MethodOfTransport implements NonVehicle{查看它在 3v4l.org 上的运行情况。}class Car extends MethodOfTransport implements Vehicle{    public function setSpeedLimit(Float $limit) : Car { return $this; }}$b = new Bicycle();$c = new Car();如前所述,我消除了命名约定的一些混乱,因为 aBicycle不是 aVehicle而是 a MethodOfTransport。interface VehicleServiceInterface{    # Change the return DataType to MethodOfTransport if Vehicle implements multiple classes    public function create(MethodOfTransport $t) : Car;}class VehicleService implements VehicleServiceInterface{    public function create(MethodOfTransport $t) : Car    {        if (!$t instanceof Vehicle)            throw new Exception( 'Cannot create a Vehicle on a NonVehicle instance.' );                    $t->setManufacturer( 'Foo' )->setSpeedLimit( 140 );        return $t;    }}$c = (new VehicleService())->create( $c ); // Works$b = (new VehicleService())->create( $b ); // Throws Exception和Car都是Bicyclea MethodOfTransport,但是,一个是 a Vehicle,另一个是 a NonVehicle。两者都可以传递到方法中,但我们可以使用 来确保它是正确的类型instanceof。但是,您可以将返回类型设置为方法的返回类型,create因为Car我们Vehicle在本例中仅过滤实例。如果您有多个Vehicle已实现的类,请将其更改为返回MethodOfTransport查看它在 3v4l.org 上的运行情况。
打开App,查看更多内容
随时随地看视频慕课网APP