Ignoriamo per un secondo che il metodo in questione è __construct
e chiamalo frobnicate
. Supponiamo ora di avere un oggetto api
che implementa IHttpApi
e un oggetto config
che implementa IHttpConfig
. Chiaramente, questo codice si adatta all'interfaccia:
$api->frobnicate($config)
Ma supponiamo di raddoppiare api
a IApi
, ad esempio passandolo a function frobnicateTwice(IApi $api)
. Ora in quella funzione, viene chiamato frobnicate
, 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 momento nessuno ha fatto nulla di sgradevole con i tipi, eppure IHttpApi::frobnicate
ha ottenuto un SpecificConfig
dove si aspettava un IHttpConfig
.
Questo non va bene. Non vogliamo proibire l'upcasting, vogliamo sottotitolare e vogliamo chiaramente più classi che 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 altro tipo generale .)
Formalmente, sei entrato in una classica trappola che circonda il polimorfismo, varianza . Non tutte le occorrenze di un tipo T
possono essere sostituite da un sottotipo U
. Al contrario, non tutte le occorrenze di un tipo T
possono essere sostituite da un supertype S
. È necessaria un'attenta considerazione (o, meglio ancora, una rigorosa applicazione della teoria dei tipi).
Tornando a __construct
: Poiché AFAIK non è possibile creare un'istanza esattamente come un'interfaccia, solo un implementatore concreto, 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à per caso speciale __construct
qui.