Ilegal no PHP: Existe um motivo de design do OOP?

16

A herança da interface abaixo é ilegal no PHP, mas acho que seria bastante útil na vida real. Existe um problema antipadrão ou documentado com o design abaixo, do qual o PHP está me protegendo?

<?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
fonte

Respostas:

22

Vamos ignorar por um segundo que o método em questão é __constructe chamá-lo frobnicate. Agora, suponha que você tenha um objeto apiimplementado IHttpApie um objeto configimplementado IHttpConfig. Claramente, esse código se encaixa na interface:

$api->frobnicate($config)

Mas vamos supor que upcast apipara IApi, por exemplo, passando-a para function frobnicateTwice(IApi $api). Agora, nessa função, frobnicateé chamado e, como ele lida apenas com IApi, ele pode executar uma chamada como $api->frobnicate(new SpecificConfig(...))onde SpecificConfigimplementa, IConfigmas não IHttpConfig. Em nenhum momento alguém fez algo desagradável com os tipos, mas IHttpApi::frobnicatechegou SpecificConfigaonde esperava a IHttpConfig.

Isso não é bom. Não queremos proibir upcasting, queremos subtipagem e queremos claramente várias classes implementando uma interface. Portanto, a única opção sensata é proibir um método de subtipo que exija tipos mais específicos de parâmetros. (Um problema semelhante ocorre quando você deseja retornar um tipo mais geral .)

Formalmente, você entrou em uma armadilha clássica em torno do polimorfismo, variação . Nem todas as ocorrências de um tipo Tpodem ser substituídas por um subtipo U. Por outro lado, nem todas as ocorrências de um tipo Tpodem ser substituídas por um supertipo S . Considerações cuidadosas (ou melhor ainda, aplicação estrita da teoria dos tipos) são necessárias.

Voltando a __construct: Como o AFAIK você não pode instanciar exatamente uma interface, apenas um implementador concreto, isso pode parecer uma restrição inútil (nunca será chamado por meio de uma interface). Mas, nesse caso, por que incluir __constructna interface, para começar? Independentemente disso, seria de pouca utilidade para casos especiais __constructaqui.


fonte
19

Sim, isso segue diretamente do Princípio de Substituição de Liskov (LSP) . Quando você substitui um método, o tipo de retorno pode se tornar mais específico, enquanto os tipos de argumentos devem permanecer os mesmos ou podem se tornar mais gerais.

Isso é mais óbvio com outros métodos que não __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 CarDriveré a Driver, portanto, uma CarDriverinstância deve ser capaz de fazer qualquer coisa que Driverpossa. Incluindo dirigir Motorcycles, porque é apenas um Vehicle. Mas o tipo de argumento para drivediz que a CarDriversó pode conduzir Cars - uma contradição: CarDriver não pode ser uma subclasse adequada de Driver.

O inverso faz mais sentido:

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

A CarDriverpode apenas dirigir Cars. A MultiTalentedDrivertambém pode conduzir Cars, porque a Caré apenas a Vehicle. Portanto, MultiTalentedDriveré uma subclasse apropriada de CarDriver.

No seu exemplo, qualquer um IApipode ser construído com um IConfig. Se IHttpApié um subtipo de IApi, devemos ser capazes de construir um IHttpApiusando qualquer IConfiginstância - mas ela aceita apenas IHttpConfig. Isso é uma contradição.

amon
fonte
Nem todos os motoristas podem dirigir carros e motos ...
sakisk
3
@ faif: Nesta abstração em particular, eles não apenas podem, mas devem. Porque, como você pode ver, um Driverpode conduzir qualquer um Vehiclee, como ambos Care Motorcyclese estende Vehicle, todos os Drivers devem ser capazes de lidar com ambos.
Alex