Ignoremos por un segundo que el método en cuestión es __construct
y lo llamamos frobnicate
. Ahora suponga que tiene un objeto api
implementando IHttpApi
y un objeto config
implementando IHttpConfig
. Claramente, este código se ajusta a la interfaz:
$api->frobnicate($config)
Pero supongamos que vamos api
a IApi
, por ejemplo, pasárselo function frobnicateTwice(IApi $api)
. Ahora en esa función, frobnicate
se llama, y dado que solo se trata IApi
, puede realizar una llamada como $api->frobnicate(new SpecificConfig(...))
where SpecificConfig
implements IConfig
pero not IHttpConfig
. En ningún momento nadie hizo nada desagradable con los tipos, pero IHttpApi::frobnicate
obtuvo un lugar SpecificConfig
donde esperaba a IHttpConfig
.
Esto no está bien. No queremos prohibir la transmisión, queremos subtipar, y claramente queremos que múltiples clases implementen una interfaz. Entonces, la única opción sensata es prohibir un método de subtipo que requiera tipos más específicos para los parámetros. (Se produce un problema similar cuando desea devolver un tipo más general ).
Formalmente, has entrado en una trampa clásica que rodea el polimorfismo, la varianza . No todas las apariciones de un tipo T
pueden reemplazarse por un subtipo U
. Por el contrario, no todas las apariciones de un tipo T
pueden reemplazarse por un supertipo S
. Es necesaria una cuidadosa consideración (o mejor aún, una aplicación estricta de la teoría de tipos).
Volviendo a __construct
: Dado que AFAIK no puede crear una instancia exacta de una interfaz, solo un implementador concreto, esto puede parecer una restricción inútil (nunca se llamará a través de una interfaz). Pero en ese caso, ¿por qué incluir __construct
en la interfaz para empezar? De todos modos, sería de poca utilidad para casos especiales __construct
aquí.
Driver
puede manejar cualquieraVehicle
, y dado que ambosCar
y seMotorcycle
extiendenVehicle
, todos losDriver
s deben poder manejar ambos.