Ilegal en PHP: ¿Hay alguna razón de diseño OOP?

16

La herencia de la interfaz a continuación es ilegal en PHP, pero creo que sería bastante útil en la vida real. ¿Existe un problema antipatrón real o documentado con el diseño a continuación, del que PHP me está protegiendo?

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

}
kojiro
fuente

Respuestas:

22

Ignoremos por un segundo que el método en cuestión es __constructy lo llamamos frobnicate. Ahora suponga que tiene un objeto apiimplementando IHttpApiy un objeto configimplementando IHttpConfig. Claramente, este código se ajusta a la interfaz:

$api->frobnicate($config)

Pero supongamos que vamos apia IApi, por ejemplo, pasárselo function frobnicateTwice(IApi $api). Ahora en esa función, frobnicatese llama, y ​​dado que solo se trata IApi, puede realizar una llamada como $api->frobnicate(new SpecificConfig(...))where SpecificConfigimplements IConfigpero not IHttpConfig. En ningún momento nadie hizo nada desagradable con los tipos, pero IHttpApi::frobnicateobtuvo un lugar SpecificConfigdonde 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 Tpueden reemplazarse por un subtipo U. Por el contrario, no todas las apariciones de un tipo Tpueden 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 __constructen la interfaz para empezar? De todos modos, sería de poca utilidad para casos especiales __constructaquí.


fuente
19

Sí, esto se deduce directamente del Principio de sustitución de Liskov (LSP) . Cuando anula un método, el tipo de retorno puede volverse más específico, mientras que los tipos de argumentos deben permanecer iguales o pueden ser más generales.

Esto es más obvio con otros métodos que no sean __construct. Considerar:

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

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

A CarDriveres a Driver, por lo que una CarDriverinstancia debe poder hacer cualquier cosa que Driverpueda. Incluyendo conducir Motorcycles, porque es solo un Vehicle. Pero el tipo de argumento para drivedice que a CarDriversolo puede conducir Cars, una contradicción: CarDriver no puede ser una subclase adecuada de Driver.

Lo contrario tiene más sentido:

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

A CarDriversolo puede conducir Cars. A MultiTalentedDrivertambién puede conducir Cars, porque a Cares solo a Vehicle. Por lo tanto, MultiTalentedDriveres una subclase adecuada de CarDriver.

En su ejemplo, cualquiera IApipuede construirse con un IConfig. Si IHttpApies un subtipo de IApi, debemos ser capaces de construir un IHttpApiusando cualquier IConfiginstancia, pero solo acepta IHttpConfig. Esto es una contradicción.

amon
fuente
No todos los conductores pueden conducir automóviles y motocicletas ...
sakisk
3
@faif: En esta abstracción en particular, no solo pueden, deben. Porque, como puede ver, un Driverpuede manejar cualquiera Vehicle, y dado que ambos Cary se Motorcycleextienden Vehicle, todos los Drivers deben poder manejar ambos.
Alex