понедельник, 15 мая 2017 г.

Наследование - это процедурный способ повторного использования кода

Мы все знаем, что наследование плохо, а композиция - хорошая идея, но действительно ли мы понимаем, почему? В большинстве статей, которые я читал по этой теме, авторы говорили, что наследование может нанести вред вашему коду, поэтому лучше его не использовать. Означает ли это, что иногда наследование имеет смысл?

Несколько недель назад я интервьюировал Дэвида Уэста (автора Object Thinking, моей любимой книги о ООП), и он сказал, что наследования  в объектно-ориентированном программировании вообще не должно быть. Возможно, доктор Уэст прав, и мы должны полностью забыть ключевое слово extends в Java.

Я думаю, что это действительно так. И я думаю, что знаю причину почему. Дело не в том, что мы вводим ненужную связку, как сказал Аллен Голуб в своей статье «Почему распространяется - зло». Он был определенно прав, но я считаю, что это не основная причина проблемы.

«Наследовать», как английский глагол, имеет ряд значений: «Воспроизводить (качество, характеристику или предрасположенность) генетически полученную от своих родителей или предков».

Получение характеристики из другого объекта - отличная идея, и она называется подтипированием. Это отлично вписывается в ООП и фактически допускает полиморфизм: объект класса Article наследует все характеристики объектов в классе Manuscript и добавляет свои собственные. Например, он наследует способность печатать сам и добавляет возможность отправки себя на конференцию:
interface Manuscript {
  void print(Console console);
}
interface Article extends Manuscript {
  void submit(Conference cnf);
}
Это подтипирование, и это совершенная техника; Всякий раз, когда требуется рукопись, мы можем предоставить статью, и никто ничего не заметит, так как тип Article является подтипом типа Manuscript (принцип замены Лискова). 

Но что такое методы и атрибуты копирования из родительского класса в дочерний, что нужно делать с «производными характеристиками»? Наследование реализации - это именно копирование - и это не имеет никакого отношения к значению слова «наследовать», которое я цитировал выше.

Наследование реализации гораздо ближе к другому смыслу: «Получить (деньги, собственность или титул) в качестве наследника при смерти предыдущего владельца». Кто умер, спросите вы? Объект мертв, если он позволяет другим объектам наследовать свой инкапсулированный код и данные. Это наследование реализации:
class Manuscript {
  protected String body;
  void print(Console console) {
    console.println(this.body);
  }
}
class Article extends Manuscript {
  void submit(Conference cnf) {
    cnf.send(this.body);
  }
}
Класс Article копирует метод print () и тело атрибута из класса Manuscript, как будто это не живой организм, а скорее мертвый, из которого мы можем наследовать его части, «деньги, свойства или титул».

Наследование реализации было создано как механизм повторного использования кода, и оно вообще не вписывается в ООП. Да, это может показаться удобным в начале, но это абсолютно неверно с точки зрения объектного мышления. Как и геттеры и сеттеры, наследование реализации превращает объекты в контейнеры с данными и процедурами. 

Конечно, удобно скопировать некоторые из этих данных и процедур в новый объект, чтобы избежать дублирования кода. Но речь идет не об объектах. Они не мертвы; Они живы! Не убивайте их наследованием :)

Таким образом, я считаю, что наследование плохо, потому что это процедурный способ повторного использования кода. Неудивительно, что он вводит все проблемы, о которых люди говорили в течение многих лет. Потому что это процедурно! Вот почему это не вписывается в объектно-ориентированное программирование.

Комментариев нет:

Отправить комментарий