Illegale in PHP: esiste un motivo di progettazione OOP?


16

L'eredità dell'interfaccia di seguito è illegale in PHP, ma penso che sarebbe abbastanza utile nella vita reale. C'è un vero e proprio antipasto o un problema documentato con il design qui sotto, da cui PHP mi sta proteggendo?

<?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);

}

Risposte:


22

Ignoriamo per un secondo che il metodo in questione è __constructe chiamiamolo frobnicate. Supponiamo ora di avere un oggetto che apiimplementa IHttpApie un oggetto che configimplementa IHttpConfig. Chiaramente, questo codice si adatta all'interfaccia:

$api->frobnicate($config)

Ma andiamo supponiamo upcast apiper IApi, ad esempio, di passarlo a function frobnicateTwice(IApi $api). Ora in quella funzione, frobnicateviene chiamato, e poiché si occupa solo di IApi, può eseguire una chiamata come $api->frobnicate(new SpecificConfig(...))dove SpecificConfigimplementa IConfigma non IHttpConfig. In nessun caso qualcuno ha fatto qualcosa di sgradevole con i tipi, ma ha IHttpApi::frobnicateottenuto un punto SpecificConfigdove 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 Tpossono essere sostituite da un sottotipo U. Al contrario, non tutte le ricorrenze di un tipo Tpossono 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 __constructnell'interfaccia per cominciare? Indipendentemente da ciò, sarebbe di scarsa utilità in __constructquesto caso speciale .


19

Sì, questo deriva direttamente dal principio di sostituzione di Liskov (LSP) . Quando si esegue l'override di un metodo, il tipo restituito può diventare più specifico, mentre i tipi di argomenti devono rimanere gli stessi o possono diventare più generali.

Questo è più ovvio con metodi diversi da __construct. Ritenere:

class Vehicle {}
class Car extends Vehicle {}
class Motorcycle extends Vehicle {}

class Driver {
    public drive(Vehicle $v) { ... }
}
class CarDriver extends Driver {
    public drive(Car $c) { ... }
}

Una CarDriverè una Driver, quindi CarDriverun'istanza deve essere in grado di fare tutto ciò che una Driverlattina. Compresa la guida Motorcycles, perché è solo un Vehicle. Ma il tipo di argomento per drivedice che a CarDriverpuò solo guidare Cars - una contraddizione: CarDriver non può essere una sottoclasse corretta di Driver.

Il contrario ha più senso:

class CarDriver {
    public drive(Car $c) { ... }
}
class MultiTalentedDriver extends CarDriver {
    public drive(Vehicle $v) { ... }
}

A CarDriverpuò solo guidare Cars. A MultiTalentedDriverpuò anche guidare Cars, perché a Carè solo a Vehicle. Pertanto, MultiTalentedDriverè una sottoclasse corretta di CarDriver.

Nel tuo esempio, chiunque IApipuò essere costruito con un IConfig. Se IHttpApiè un sottotipo di IApi, dobbiamo essere in grado di costruire un IHttpApiusando qualsiasi IConfigistanza, ma accetta solo IHttpConfig. Questa è una contraddizione.


Non tutti i conducenti possono guidare sia auto che moto ...
sakisk,

3
@faif: in questa particolare astrazione, non solo possono, ma devono. Perché, come puoi vedere, Driverpuoi guidare qualsiasi Vehicle, e dato che entrambi Care si Motorcycleestendono Vehicle, tutti gli utenti Driverdevono essere in grado di gestirli entrambi.
Alex,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.