web-dev-qa-db-ja.com

PHPでの違法:OOP設計上の理由はありますか?

以下のインターフェースの継承は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);

}
16
kojiro

問題のメソッドが___construct_であることを少し無視して、frobnicateと呼びます。ここで、apiを実装するオブジェクトIHttpApiと、configを実装するオブジェクトIHttpConfigがあるとします。明らかに、このコードはインターフェースに適合します:

_$api->frobnicate($config)
_

ただし、apiIApiにアップキャストするとします。たとえば、function frobnicateTwice(IApi $api)に渡します。この関数では、frobnicateが呼び出され、IApiのみを処理するため、$api->frobnicate(new SpecificConfig(...))などの呼び出しを実行できます。ここで、SpecificConfigIConfigを実装しますが、IHttpConfigは実装しません。どの時点でも、型で不快なことをしたことはありませんが、_IHttpApi::frobnicate_はSpecificConfigを期待していたところにIHttpConfigを取得しました。

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

正式には、ポリモーフィズムを取り巻く古典的な罠 variance に入りました。タイプTのすべての出現箇所をサブタイプUで置き換えることができるわけではありません。逆に、型Tのすべての出現箇所をsupertypeSで置き換えることができるわけではありません。慎重に検討する必要があります(さらには、型理論を厳密に適用する必要があります)。

___construct_に戻る:AFAIKはインターフェイスを正確にインスタンス化することはできないため、具体的な実装者のみであり、これは無意味な制限のように思えるかもしれません(インターフェイスから呼び出されることは決してありません)。しかし、その場合、最初に___construct_をインターフェイスに含める理由を教えてください。とにかく、ここでは特殊なケースの___construct_はほとんど役に立ちません。

22
user7043

はい、これは Liskov Substitution Principle(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の引数の型は、CarDriverCarsのみを駆動できると述べています–矛盾:CarDriverは、Driverの適切なサブクラスにすることはできません

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

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

CarDriverは、Carsのみを駆動できます。 MultiTalentedDriverは単なるCarなので、CarVehiclesを駆動することもできます。したがって、MultiTalentedDriverCarDriverの適切なサブクラスです。

あなたの例では、IApiIConfigを構築できます。 IHttpApiIApiのサブタイプである場合、IHttpApiインスタンスを使用してIConfigを構築できる必要がありますが、IHttpConfigのみを受け入れます。これは矛盾です。

19
amon