Ignoriamo per un secondo che il metodo in questione è __construct
e chiamiamolo frobnicate
. Supponiamo ora di avere un oggetto che api
implementa IHttpApi
e un oggetto che config
implementa IHttpConfig
. Chiaramente, questo codice si adatta all'interfaccia:
$api->frobnicate($config)
Ma andiamo supponiamo upcast api
per IApi
, ad esempio, di passarlo a function frobnicateTwice(IApi $api)
. Ora in quella funzione, frobnicate
viene chiamato, e poiché si occupa solo di IApi
, può eseguire una chiamata come $api->frobnicate(new SpecificConfig(...))
dove SpecificConfig
implementa IConfig
ma non IHttpConfig
. In nessun caso qualcuno ha fatto qualcosa di sgradevole con i tipi, ma ha IHttpApi::frobnicate
ottenuto un punto SpecificConfig
dove si aspettava a IHttpConfig
.
Questo non va bene. Non vogliamo vietare l'upgrade, vogliamo il sottotitolo e vogliamo chiaramente che più classi implementino un'interfaccia. Quindi l'unica opzione sensata è proibire un metodo di sottotipo che richiede tipi più specifici per i parametri. (Un problema simile si verifica quando si desidera restituire un tipo più generale .)
Formalmente, sei entrato in una classica trappola che circonda il polimorfismo, la varianza . Non tutte le ricorrenze di un tipo T
possono essere sostituite da un sottotipo U
. Al contrario, non tutte le ricorrenze di un tipo T
possono essere sostituite da un supertipo S
. È necessaria un'attenta considerazione (o meglio, rigorosa applicazione della teoria dei tipi).
Tornando a __construct
: Dal momento che AFAIK non è possibile creare un'istanza di un'interfaccia, ma solo un implementatore concreto, questa può sembrare una limitazione inutile (non verrà mai chiamata attraverso un'interfaccia). Ma in tal caso, perché includere __construct
nell'interfaccia per cominciare? Indipendentemente da ciò, sarebbe di scarsa utilità in __construct
questo caso speciale .