Незаконне в PHP: Чи є причина дизайну OOP?


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є Driver, тому CarDriverекземпляр повинен бути в змозі зробити все, що Driverможе. У тому числі за кермом Motorcycles, тому що це просто Vehicle. Але тип аргументу для driveговорить, що a CarDriverможе керувати лише Cars - протиріччя: CarDriver не може бути належним підкласом Driver.

Реверс має більше сенсу:

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

A CarDriverможе керувати лише Cars. А MultiTalentedDriverтакож можна керувати Cars, тому що a Car- це просто a Vehicle. Тому MultiTalentedDriverє належним підкласом CarDriver.

У вашому прикладі будь-яке IApiможе бути побудовано за допомогою IConfig. Якщо IHttpApiце підтип IApi, ми повинні мати можливість побудувати за IHttpApiдопомогою будь-якого IConfigпримірника - але він лише приймає IHttpConfig. Це суперечність.


Не всі водії можуть керувати і автомобілями, і мотоциклами ...
sakisk

3
@faif: У цій конкретній абстракції вони не тільки можуть, але й повинні. Тому що, як ви бачите, Driverможе керувати будь-яким Vehicle, а оскільки і те, Carі Motorcycleрозширюється Vehicle, всі Drivers повинні мати можливість поводитися з обома.
Олексій
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.