PHP中的非法:是否存在OOP设计原因?


16

下面的接口继承在PHP中是非法的,但我认为它在现实生活中将非常有用。下面的设计是否存在实际的反模式或已记录的问题,PHP正在保护我免受其侵扰?

<?php

/**
 * Marker interface
 */
interface IConfig {}

/**
 * An api sdk tool
 */
interface IApi
{
    public __construct(IConfig $cfg);
}

/**
 * Api configuration specific to http
 */
interface IHttpConfig extends IConfig
{
    public getSomeNiceHttpSpecificFeature();
}

/**
 * Illegal, but would be really nice to have.
 * Is this not allowed by design?
 */
interface IHttpApi extends IApi
{
    /**
     * This constructor must have -exactly- the same
     * signature as IApi, even though its first argument
     * is a subtype of the parent interface's required
     * constructor parameter.
     */
    public __construct(IHttpConfig $cfg);

}

Answers:


22

让我们先忽略一下所涉及的方法,__construct然后调用它frobnicate。现在假设您有一个对象api实现IHttpApi和一个对象config实现IHttpConfig。显然,此代码适合接口:

$api->frobnicate($config)

但是让我们假设我们上溯造型apiIApi,例如它传递给function frobnicateTwice(IApi $api)。现在在该函数中frobnicate被调用,并且由于它只处理IApi,因此它可以执行诸如$api->frobnicate(new SpecificConfig(...))where SpecificConfigImplements IConfig而不是where 的调用IHttpConfig。任何人在任何时候都不会对类型做任何不好的事情,但是却IHttpApi::frobnicate达到了SpecificConfig预期的位置IHttpConfig

不好 我们不想禁止向上转换,我们想要子类型化,并且我们显然希望多个类实现一个接口。因此,唯一明智的选择是禁止子类型方法需要更多特定类型的参数。(当您想返回通用的类型时,也会发生类似的问题。)

正式地,您走进了围绕多态性variance的经典陷阱。并非所有类型的出现T都可以用子类型代替U。相反,并非所有出现的类型T都可以由超类型 代替S。必须仔细考虑(或者更好的是,严格应用类型理论)。

回到__construct:由于AFAIK您不能完全实例化一个接口,而只能是一个具体的实现者,这似乎是没有意义的限制(永远不会通过接口调用它)。但是在那种情况下,为什么要包含__construct在界面中呢?无论如何,__construct在这里特殊情况下用处不大。


19

是的,这直接遵循Liskov替代原理(LSP)。覆盖方法时,返回类型可以变得更具体,而参数类型必须保持相同或变得更通用。

使用以外的方法,这一点更加明显__construct。考虑:

class Vehicle {}
class Car extends Vehicle {}
class Motorcycle extends Vehicle {}

class Driver {
    public drive(Vehicle $v) { ... }
}
class CarDriver extends Driver {
    public drive(Car $c) { ... }
}

一个CarDriver是一个Driver,所以CarDriver实例必须能够做任何事情,一个Driver即可。包括驾驶在内Motorcycles,因为它只是一个Vehicle。但是for的参数类型drive表示a CarDriver只能驱动Cars –一个矛盾:CarDriver 不能是的适当子类Driver

反之则更有意义:

class CarDriver {
    public drive(Car $c) { ... }
}
class MultiTalentedDriver extends CarDriver {
    public drive(Vehicle $v) { ... }
}

A CarDriver只能驱动Car。A MultiTalentedDriver也可以驱动Cars,因为a Car只是a Vehicle。因此,MultiTalentedDriver是的适当子类CarDriver

在您的示例中,任何IApi都可以使用构造IConfig。如果IHttpApi是的子类型IApi,我们必须能够IHttpApi使用任何IConfig实例来构造一个,但它只能接受IHttpConfig。这是一个矛盾。


并非所有驾驶员都可以驾驶汽车和摩托车...
sakisk 2014年

3
@faif:在这种特定的抽象中,它们不仅可以,而且必须。因为,正如你所看到的,Driver可以驾驶任何Vehicle,并且因为这两个CarMotorcycle延伸Vehicle,所有的Drivers必须能够同时处理。
Alex
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.