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

в 22:06, , рубрики: C#, java, php, архитектура, ооп, проектирвоание, Проектирование и рефакторинг, Совершенный код

Перевод ироничного поста из блога Боба Мартина в котором он рассуждает о том, насколько неудачным является использование слова 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 — вреден и губителен!

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

Автор: Артур Пантелеев

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js