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);

}

回答:


22

問題のメソッドが存在することを少し無視して__construct、呼び出しましょうfrobnicate。今、あなたは、オブジェクトがあるとapi実装IHttpApi、およびオブジェクトconfigの実装をIHttpConfig。明らかに、このコードはインターフェースに適合しています。

$api->frobnicate($config)

しかし、たとえば、に渡すなど、アップキャストapiするIApiとしfunction frobnicateTwice(IApi $api)ます。これで、その関数でfrobnicateが呼び出され、のみを処理IApiするため、$api->frobnicate(new SpecificConfig(...))where SpecificConfigimplementsでIConfigなく、などの呼び出しを実行できIHttpConfigます。誰もタイプについて不快なことをしたことはありませんIHttpApi::frobnicateが、SpecificConfig期待どおりの場所を取得しましたIHttpConfig

これはダメです。アップキャスティングを禁止したくない、サブタイプをしたい、そして明らかにインターフェイスを実装する複数のクラスが欲しい。したがって、唯一の賢明なオプションは、パラメーターに対してより具体的な型を必要とするサブタイプメソッドを禁止することです。(より一般的な型を返す場合も同様の問題が発生します。)

正式には、多型、分散を取り巻く古典的なtrapに陥りました。タイプのすべての出現をTサブタイプで置き換えることができるわけではありませんU。逆に、ある型のすべての出現をスーパータイプにT置き換えることができるわけではありません。慎重に検討する(または、さらに良いことに、型理論を厳密に適用する)必要があります。 S

戻る__construct:AFAIKではインターフェイスを正確にインスタンス化することはできず、具体的な実装者であるため、これは無意味な制限のように見えるかもしれません(インターフェイスを介して呼び出されることはありません)。しかし、その場合、__construct最初にインターフェイスに含めるのはなぜですか?とにかく、__constructここでの特別な場合にはほとんど役に立ちません。


19

はい、これはリスコフ代替原理(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) { ... }
}

CarDriverDriver、そのCarDriverインスタンスが行うことができなければならない何かをすることをDriverすることができます。運転を含むMotorcycles、それはただですのでVehicle。しかし、の引数タイプdriveは、CarDriverを駆動できるのはCars のみであると言います-矛盾:の適切なサブクラスにはなりCarDriver えませんDriver

逆の方が理にかなっています:

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

A CarDriverCars のみを駆動できます。AMultiTalentedDriverまた、駆動することができますCarので、秒CarだけですVehicle。したがって、MultiTalentedDriverはの適切なサブクラスですCarDriver

あなたの例では、any IApiはで構築できますIConfigIHttpApiがのサブタイプである場合、任意のインスタンスを使用してIApiを構築できる必要がありますが、受け入れられるのはIHttpApiIConfigIHttpConfig。これは矛盾です。


すべてのドライバーが車とオートバイの両方を運転できるわけではありません...
sakisk

3
@faif:この特定の抽象化では、できるだけでなく、する必要があります。あなたが見ることができるように、a Driverはanyを駆動することができVehicle、両方CarMotorcycleextendsであるためVehicle、すべてDriverのsが両方を処理できる必要があるためです。
アレックス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.