Lassen Sie uns für eine Sekunde ignorieren, dass es sich um die betreffende Methode handelt, __construct
und rufen Sie sie auf frobnicate
. Angenommen, Sie haben ein Objekt api
implementiert IHttpApi
und ein Objekt config
implementiert IHttpConfig
. Dieser Code passt eindeutig zur Benutzeroberfläche:
$api->frobnicate($config)
Aber nehmen wir api
an IApi
, wir sind verärgert , um es zum Beispiel weiterzugeben function frobnicateTwice(IApi $api)
. Nun wird in dieser Funktion frobnicate
aufgerufen, und da es sich nur IApi
um eine Funktion handelt , kann es einen Aufruf ausführen, wie beispielsweise $api->frobnicate(new SpecificConfig(...))
where SpecificConfig
implementiert, IConfig
aber nicht IHttpConfig
. Zu keinem Zeitpunkt tat jemand etwas Unangenehmes mit Typen, IHttpApi::frobnicate
bekam aber einen, SpecificConfig
wo er einen erwartete IHttpConfig
.
Das ist nicht gut. Wir wollen Upcasting nicht verbieten, wir wollen Subtyping und wir wollen eindeutig mehrere Klassen, die eine Schnittstelle implementieren. Daher ist die einzig sinnvolle Option, eine Subtyp-Methode zu verbieten, die spezifischere Typen für Parameter erfordert . (Ein ähnliches Problem tritt auf, wenn Sie wollen zurückkehren einen allgemeinen Typ.)
Formal sind Sie in eine klassische Falle geraten, die sich mit Polymorphismus und Varianz befasst . Nicht alle Vorkommen eines Typs T
können durch einen Untertyp ersetzt werden U
. Umgekehrt können nicht alle Vorkommen eines Typs T
durch einen Supertyp ersetzt werden S
. Sorgfältige Überlegungen (oder noch besser strikte Anwendung der Typentheorie) sind erforderlich.
Zurück zu __construct
: Da Sie mit AFAIK eine Schnittstelle nicht genau instanziieren können, sondern nur einen konkreten Implementierer, scheint dies eine sinnlose Einschränkung zu sein (sie wird niemals über eine Schnittstelle aufgerufen). Aber warum sollte man in diesem Fall zunächst __construct
in die Benutzeroberfläche aufnehmen? Unabhängig davon wäre es hier von geringem Nutzen, Sonderfälle zu machen __construct
.