Pull to refresh

Вредный Кейворд «Interface»

PHP *Java *Designing and refactoring *C# *ООP *
Translation
Original author: Robert C. Martin (Uncle Bob)

Перевод ироничного поста из блога Боба Мартина в котором он рассуждает о том, насколько неудачным является использование слова interface в современных языках программирования, и какую путаницу и проблемы оно несёт разработчикам.


— Что ты думаешь об интерфейсах?


Имеешь в виду интерфейсы в Java или C#?


— Да. Классная фича этих языков?


Просто великолепная!


— Правда? А что такое интерфейс? Это то же самое что и класс?


Ну… Не совсем!


— В каком плане?


Ни один из его методов не должен иметь реализации.


— Значит это интерфейс?


public abstract class MyInterface {
  public abstract void f();
}

Нет, это абстрактный класс.


— Так, а в чём разница?


Абстрактный класс может иметь реализованные методы.


— Да, но этот класс их не имеет. Тогда почему его нельзя назвать интерфейсом?


Абстрактный класс может иметь нестатические поля, а интерфейс не может.


— У моего класса их тоже нет, почему он не интерфейс?


Потому что!


— Такой себе ответ… В чем реальное отличие от интерфейса? Что такого можно делать с интерфейсом, чего нельзя делать с этим классом?


Класс, который наследуется от другого, не может унаследоваться от твоего.


— Почему?


Потому что в Java нельзя наследоваться от нескольких классов.


— А почему?


Компилятор тебе не позволит.


— Очень странно. Тогда почему я могу реализовать(implements), а не отнаследоваться(extend) от него?


Потому что компилятор позволяет множественную реализацию интерфейсов, но наследовать ты можешь только один класс.


— Интересно, зачем такие ограничения?


Потому что наследование от множества классов опасно.


— Вот так новости! И чем же?


Смертельным Бриллиантом Смерти(Deadly Diamond of Death)!


— Звучит пугающе! Но что это значит?


Это когда класс наследует два других класса, оба и которых наследуют третий.


— Ты имеешь ввиду что-то типо этого?


class B {}
class D1 extends B {}   
class D2 extends B {}   
class M extends D1, D2 {}

Верно, это очень плохо!


— Почему?


Потому что класс B может содержать переменные!


— Вот так?


class B {private int i;}

Да! И как много переменных i будет в экземпляре класса M?


— Понятно. Т.е раз D1 и D2 содержат переменную i, a M наследуется от D1 и D2 то ты ожидаешь, что экземпляр класса M должен иметь две разных переменные i?


Да! Но т.к M наследуется от B у которого только одна переменная i, то ты ожидаешь что в M у тебя тоже будет всего одна i.


— Вот так неоднозначность.


Да!


— Получается что Java(и C#) не могут во множественное наследование классов, потому что кто-то может создать "Смертельный Бриллиант Смерти"?


Не просто может создать. Каждый априори создавал бы их т.к все объекты неявно наследуют Object.


— Ясно. А авторы компилятора не могли пометить Object как частный случай?


Ну… не пометили.


— Интересно почему. А как решается эта проблема в других компиляторах?


Компилятор C++ позволяет делать это


— Я думаю Eiffel тоже.


Черт, даже в Ruby смогли решить эту проблему!


— Ладно, получается что "Смертельный Бриллиант Смерти" это проблема, которую решили еще в прошлом веке, и она не фатальна и даже не ведёт к смерти.


Вынужден согласиться.


— Давай вернемся к первоначальному вопросу. Почему это не интерфейс?


public abstract class MyInterface {
      public abstract void f();
}

Потому что он использует кейворд class и язык не позволит унаследоваться от множества классов.


— Верно. Получается, что кейворд interface был изобретен для предотвращения множественного наследования классов?


Да, наверное.


— Так почему бы разработчикам Java (да и C#) не воспользоваться любым из решений проблемы множественного наследования?


Откуда я знаю?


— Кажется я знаю.


??


— Лень!


Лень?


— Да, им было лень разбираться с проблемой. Вот они и создали новую фичу, которая позволила им не решать её. Этой фичей стал interface.


Т.е ты хочешь сказать, что разработчики Java ввели понятие интерфейса чтобы избежать лишней работы?


— У меня нет другого объяснения!


Звучит грубовато. Да и в любом случае, круто что у нас есть интерфейсы. Они тебе чем нибудь мешают?


— Ответь себе на вопрос: Почему класс должен знать что он реализует именно интерфейс? Разве это не должно быть скрыто от него?


Имеешь в виду, что производный тип должен знать что именно он делает — наследует или реализует(extends or implements)?


— Абсолютно! И если ты поменяешь класс на интерфейс, то в код скольки наследников придется вносить изменения?


Во всех. В Java во всяком случае. В C# разобрались хотя бы с этой проблемой.


— Да уж. Ключевые слова implements и extends излишни и опасны. Было бы лучше если бы Java использовала решения C# или C++.


Ладно, ладно. Но когда тебе реально нужно было множественное наследование?


— Я бы хотел делать так:


public class Subject {
    private List<Observer> observers = new ArrayList<>();
    private void register(Observer o) {
        observers.add(o);
    }
    private void notify() {
        for (Observer o : observers)
            o.update();
    }
}

public class MyWidget {...}

public class MyObservableWidget extends MyWidget, Subject {
    ...
}

Это же паттерн "Наблюдатель"!


— Да. Это правильный "Наблюдатель".


Но он не скомпилируется, т.к ты пытаешься отнаследоваться от двух классов сразу.


— Да, в этом и трагедия.


Трагедия? Какого… Ты же можешь просто унаследовать MyWidget от Subject!


— Но я не хочу, чтобы MyWidget знал что за ним наблюдают. Мне бы хотелось разделять ответственности(separation of concerns). Быть наблюдаемым и быть виджетом, это две совершенно разные ответственности.


Тогда просто реализуй функции регистраци и уведомления в MyObservableWidget.


— Что? Дублировать код для каждого наблюдаемого класса? Нет спасибо!


Тогда пусть твой MyObservableWidget содержит ссылку на Subject и делегирует ему все что нужно.


— И дублировать код делегирования? Это какая-то фигня.


Но тебе все равно придется выбрать что-то из предложенного


— Знаю. Но я ненавижу это.


У тебя нет выхода. Либо нарушай разделение ответственностей, либо дублируй код.


— Да. Это язык сам толкает меня в это дерьмо.


Да, это очень грустно.


— И?


Могу лишь сказать, что кейворд interface — вреден и губителен!


Буду признателен за ошибки и замечания в ЛС.

Tags:
Hubs:
Total votes 87: ↑62 and ↓25 +37
Views 29K
Comments 389
Comments Comments 389