以下のインターフェースの継承は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);
}
問題のメソッドが___construct
_であることを少し無視して、frobnicate
と呼びます。ここで、api
を実装するオブジェクトIHttpApi
と、config
を実装するオブジェクトIHttpConfig
があるとします。明らかに、このコードはインターフェースに適合します:
_$api->frobnicate($config)
_
ただし、api
をIApi
にアップキャストするとします。たとえば、function frobnicateTwice(IApi $api)
に渡します。この関数では、frobnicate
が呼び出され、IApi
のみを処理するため、$api->frobnicate(new SpecificConfig(...))
などの呼び出しを実行できます。ここで、SpecificConfig
はIConfig
を実装しますが、IHttpConfig
は実装しません。どの時点でも、型で不快なことをしたことはありませんが、_IHttpApi::frobnicate
_はSpecificConfig
を期待していたところにIHttpConfig
を取得しました。
これはダメです。アップキャストを禁止したくない、サブタイピングしたい、そしてインターフェースを実装する複数のクラスが明らかに欲しい。したがって、唯一の賢明なオプションは、パラメータにより具体的なタイプを必要とするサブタイプメソッドを禁止することです。 (returnmoregeneralタイプにしたい場合にも同様の問題が発生します。)
正式には、ポリモーフィズムを取り巻く古典的な罠 variance に入りました。タイプT
のすべての出現箇所をサブタイプU
で置き換えることができるわけではありません。逆に、型T
のすべての出現箇所をsupertypeS
で置き換えることができるわけではありません。慎重に検討する必要があります(さらには、型理論を厳密に適用する必要があります)。
___construct
_に戻る:AFAIKはインターフェイスを正確にインスタンス化することはできないため、具体的な実装者のみであり、これは無意味な制限のように思えるかもしれません(インターフェイスから呼び出されることは決してありません)。しかし、その場合、最初に___construct
_をインターフェイスに含める理由を教えてください。とにかく、ここでは特殊なケースの___construct
_はほとんど役に立ちません。
はい、これは 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) { ... }
}
CarDriver
はDriver
であるため、CarDriver
インスタンスは、Driver
で可能なことをすべて実行できる必要があります。 Motorcycles
にすぎないため、Vehicle
の駆動を含みます。しかし、drive
の引数の型は、CarDriver
はCar
sのみを駆動できると述べています–矛盾:CarDriver
は、Driver
の適切なサブクラスにすることはできません。
逆の方が理にかなっています:
class CarDriver {
public drive(Car $c) { ... }
}
class MultiTalentedDriver extends CarDriver {
public drive(Vehicle $v) { ... }
}
CarDriver
は、Car
sのみを駆動できます。 MultiTalentedDriver
は単なるCar
なので、Car
はVehicle
sを駆動することもできます。したがって、MultiTalentedDriver
はCarDriver
の適切なサブクラスです。
あなたの例では、IApi
でIConfig
を構築できます。 IHttpApi
がIApi
のサブタイプである場合、IHttpApi
インスタンスを使用してIConfig
を構築できる必要がありますが、IHttpConfig
のみを受け入れます。これは矛盾です。