Наследование или композиция?
17508

Наследование или композиция?


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

Наследование


Когда дочерний класс наследует от родительского класса, дочерний класс приобретает все поведенческие характеристики родительского класса. Наследование создает иерархию классов - вы можете представить ее как дерево классов.

Композиция


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

Мы можем представить композицию как игру с конструктором Лего, а компоненты - это кирпичики Лего.

Наследование и композиция


Основное различие между наследованием и композицией заключается в отношениях между объектами.

Наследование: "является". Например, автомобиль - это транспортное средство.

Композиция: "имеет". Например, у автомобиля есть руль.

Наследование известно как самая тесная форма связи в объектно-ориентированном программировании. Изменение базового класса может вызвать нежелательные побочные эффекты в его подклассах или даже во всей кодовой базе.

Композиция - это гораздо более слабая связь. В сочетании с внедрением зависимости (Dependency injection, DI) она обеспечивает большую гибкость, а также позволяет нам изменять поведение во время выполнения программы.

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

Такой подход позволяет легче приспособиться к будущим изменениям требований, которые могут потребовать полной перестройки дерева классов в подходе наследования. Мы можем просто добавить новый компонент в объединенный класс, а не модифицировать суперкласс для адаптации изменений.

Они также отличаются по назначению.

Наследование: Конструирование класса на основе того, чем он является.

Композиция: Создание класса на основе того, что он делает.

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

Проблемы использования наследования


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

Иерархию классов очень трудно изменить после ее развертывания. Одним из вариантов было бы переписать все заново.

Иногда мы действительно используем наследование не по назначению. Принцип подстановки Барбары Лисков (Liskov Substitution Principle, LSP) является наиболее важным руководством для определения того, подходит ли наследование для вашего проекта или нет.

Например: B наследуется от A. Используйте подстановку Лисков, чтобы обосновать связь между B и A, затем измените связь и снова обоснуйте ее. Если отношения выглядят логичными в обоих направлениях, лучше не использовать наследование.

При наследовании вы можете рассмотреть, что делает суперкласс, а также то, что ваши подклассы могут захотеть отменить/изменить. Вместо того чтобы сосредоточиться на своей области применения, вам, возможно, придется потратить время на понимание большого количества кода во всем вашем приложении, чтобы просто "заставить" его работать хорошо.

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

Еще одним недостатком наследования является то, что оно значительно затрудняет отладку, когда иерархия классов становится слишком сложной. Простая трассировка стека может вылиться за пределы экрана при наличии 30 уровней наследования.

Заключение


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

Выбор инструмента программирования подобен выбору правильного кухонного инструмента: вы не будете использовать нож для масла, чтобы нарезать овощи, и точно так же не стоит выбирать композицию для каждого сценария программирования.

Используйте наследование, когда оно действительно необходимо, а не просто для повторного использования кода. Когда вы думаете об использовании наследования, спросите себя, действительно ли подкласс является более специализированной версией суперкласса, иначе в один прекрасный день вы столкнетесь с неразберихой.

Когда речь заходит о наследовании, всегда спрашивайте себя еще раз: "Не имеет ли большего смысла использовать композицию?".

Получать оповещения о новых статьях:


Не нашли нужную статью? Предложите свою тему