Нелегально в PHP: есть ли причина разработки ООП?

16

Приведенное ниже наследование интерфейса недопустимо в PHP, но я думаю, что это было бы весьма полезно в реальной жизни. Есть ли настоящая антипаттерн или задокументированная проблема с приведенным ниже дизайном, от которой PHP защищает меня?

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

}
Кодзиро
источник

Ответы:

22

Давайте на секунду проигнорируем, что это за метод, __constructи назовем его frobnicate. Теперь предположим, что у вас есть apiреализация объекта IHttpApiи configреализация объекта IHttpConfig. Очевидно, этот код соответствует интерфейсу:

$api->frobnicate($config)

Но давайте предположим, что мы настроены apiна передачу IApi, например, передав ее function frobnicateTwice(IApi $api). Теперь в этой функции frobnicateвызывается, и поскольку она имеет дело только с IApi, она может выполнить вызов, например, $api->frobnicate(new SpecificConfig(...))где SpecificConfigреализует, IConfigно не IHttpConfig. Ни в коем случае никто не делал ничего плохого с типами, но IHttpApi::frobnicateполучил то, SpecificConfigчего ожидал IHttpConfig.

Это не хорошо. Мы не хотим запрещать апкастинг, нам нужны подтипы, и мы явно хотим, чтобы несколько классов реализовывали интерфейс. Поэтому единственный разумный вариант - запретить метод подтипов, требующий более специфических типов для параметров. (Подобная проблема возникает, когда вы хотите вернуть более общий тип.)

Формально вы попали в классическую ловушку, окружающую полиморфизм, дисперсию . Не все вхождения типа Tмогут быть заменены подтипом U. И наоборот, не все вхождения типа Tмогут быть заменены супертипом S . Необходимо тщательное рассмотрение (или, что еще лучше, строгое применение теории типов).

Возвращаясь к __construct: Поскольку AFAIK вы не можете точно создать экземпляр интерфейса, только конкретную реализацию, это может показаться бессмысленным ограничением (его никогда не вызовут через интерфейс). Но в таком случае зачем включать __constructв интерфейс для начала? Несмотря на это, это было бы мало полезно для особого случая __constructздесь.


источник
19

Да, это следует непосредственно из принципа подстановки Лискова (LSP) . При переопределении метода возвращаемый тип может стать более конкретным, в то время как типы аргументов должны оставаться неизменными или могут стать более общими.

Это более очевидно с другими методами, чем __construct. Рассмотреть возможность:

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, поэтому CarDriverэкземпляр должен иметь возможность делать все, что Driverможет. Включая вождение Motorcycles, потому что это просто Vehicle. Но тип аргумента for driveговорит о том, что a CarDriverможет управлять только Cars - противоречие: CarDriver не может быть правильным подклассом Driver.

Обратное имеет больше смысла:

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

А CarDriverможет управлять только Carс. А MultiTalentedDriverтакже может управлять Cars, потому что это Carпросто Vehicle. Следовательно, MultiTalentedDriverявляется правильным подклассом CarDriver.

В вашем примере, любой IApiможет быть построен с IConfig. Если IHttpApiэто подтип IApi, мы должны быть в состоянии создать IHttpApiиспользование любого IConfigэкземпляра - но он только принимает IHttpConfig. Это противоречие.

Амон
источник
Не все водители могут водить как автомобили, так и мотоциклы ...
Сакиск
3
@faif: В этой конкретной абстракции они не только могут, но и должны. Потому что, как вы можете видеть, a Driverможет управлять любым Vehicle, и поскольку оба Carи Motorcycleрасширяются Vehicle, все Drivers должны быть в состоянии справиться с обоими.
Алекс