Наследование против композиции в JavaScript
5 апреля 2022 г.Один из наиболее часто задаваемых вопросов: «Что мне использовать, наследование или композицию». Это распространенный вопрос, и в целом он касается не только JavaScript, но здесь мы обсудим этот вопрос на примерах JS.
Прежде чем читать дальше, если вы не знаете, что такое Композиция или что такое Наследование, я настоятельно рекомендую вам прочитать или посмотреть что-нибудь об этих подходах, потому что основная цель этой статьи , чтобы показать вам, что и когда будет лучше выбрать. Но, чтобы убедиться, что мы на одной волне, давайте немного обсудим, что к чему.
Наследование — это одна из основных концепций объектно-ориентированного программирования, которая помогает нам, разработчикам, избегать дублирования кода. Основная идея заключается в том, что мы создаем базовый класс, содержащий логику, которая будет повторно использоваться нашими подклассами. Давайте посмотрим на пример:
Из примера видно, что у нас есть базовый класс “Element”, и все наши будущие элементы будут расширять общую логику класса Element.
Кроме того, может быть, вы слышали об отношениях is-ass. Что это значит? Например, «Форма» — это «Элемент», а «Кнопка» — это «Элемент».
Другой пример: мы можем создать базовый класс «Животное» и наши подклассы, такие как «Птица», «Собака», «Кошка». ” также имеет отношение is-a. Почему? Поскольку «Птица» — это «Животное», «Собака» — это «Животное» и «Кошка » также является «животным**».
А теперь поговорим немного о композиции. В отличие от наследования, в композиции используется отношение имеет-а. Мы собираем разные кусочки функциональности вместе. Давайте посмотрим на пример:
В этом примере мы видим, что у нас есть основной класс «Car», который использует «Engine» и «Transmission».
В этом примере мы не можем сказать, что «Двигатель» — это «Автомобиль» или «Трансмиссия» — это «Автомобиль». ». Но мы можем сказать, что «Автомобиль» имеет «Трансмиссия» или «Автомобиль» имеет «Двигатель».
Я надеюсь, что вы понимаете, что такое Наследование и что такое Композиция, если вы забыли.
Итак, теперь давайте рассмотрим разные примеры. Сначала мы рассмотрим пример с использованием подхода с использованием классов, а затем рассмотрим подход с использованием функций.
Представьте, мы работаем с файловой системой и хотим создать функции чтения, записи и удаления. Что мы можем сделать? Просто, например, мы можем создать класс, который будет это делать, и ничего страшного:
Я думаю, что на данный момент все в порядке с этим подходом.
Но, чуть позже, мы понимаем, что мы хотим иметь разрешения, и у кого-то из наших пользователей будет только разрешение на чтение, у другого — на запись. Что мы можем сделать? Одним из решений является разделение наших методов на разные классы, например:
И теперь мы можем использовать необходимое разрешение для каждого клиента. Но теперь у нас есть проблема. Что, если нам нужно дать кому-то разрешение на чтение и на запись? Или для чтения и для удаления? С текущей реализацией мы не можем этого сделать. Как мы можем это решить?
Первым решением может быть: создать класс для чтения и записи или для чтения и удаления:
Если мы это сделаем, у нас будут следующие классы: FileReader, FileWriter, FileRemove, FileReaderAndWriter, FileReaderAndRemover.
И это плохо. Почему? Первая причина, если у нас не 3 метода, а 10, 20. У нас может быть очень большое количество комбинаций между ними. Вторая причина в том, что мы дублируем логику в наших классах. Просто, например, наш класс FileReader содержит методы чтения, а FileReaderAndWriter также содержит дублирующийся код того же метода.
Так что, как видите, это не самое лучшее решение.
У нас есть другой подход? Как насчет множественного наследования? Во-первых, у нас нет этой фичи в Javascript, а если и есть, то это тоже нехорошо, потому что у нас будет реально запутанный дизайн, когда 1 класс наследуется от другого, а те другие наследуются от многих других. Это будет действительно ужасная архитектура кода.
Итак, каково решение? Одним из возможных решений здесь является использование композиции. Как это будет выглядеть?
Прежде всего, давайте переместим наши методы в отдельную фабрику функций. Почему функция? Потому что, возможно, мы захотим передать некоторые параметры позже. В нашем примере он нам не нужен.
В приведенном выше примере у нас есть 2 функции, которые создают объекты с помощью метода чтения или записи.
Что это нам дает? Теперь мы можем использовать их в любом месте, где захотим, и комбинировать их:
В приведенном выше примере мы создали службу чтения и записи. Это почти то, что мы имеем в начале нашего класса FileReader (только без метода «удалить»).
И теперь, если мы хотим совместить разные разрешения: для чтения и записи или записи и удаления, мы можем сделать это очень легко:
Если у нас есть 5, 10, 20 методов, мы можем комбинировать их как хотим. У нас нет проблем с дублированием кода, у нас нет запутанной архитектуры.
Теперь давайте посмотрим на другой пример, без классов. Представьте, что у нас есть сотрудники. Например: водитель такси, спортивный тренер и менеджер:
И теперь все в порядке. Но представьте себе ситуацию, когда какой-то сотрудник работает спортивным тренером, но иногда или ночью подрабатывает еще и таксистом. Что мы можем сделать?
Создайте функцию, например:
Да, работать будет, но проблемы те же, что и в первом примере с классами. У нас может быть действительно много разных комбинаций с дублированием кода. Итак, зная теперь, как работает композиция, мы можем реорганизовать наш код, как здесь:
И теперь мы можем комбинировать все наши виды работ как хотим, без дублирования и с понятным подходом:
Я очень надеюсь, что теперь вы видите разницу между наследованием и композицией.
В общем, наследование может отвечать на отношение «есть-а», когда композиция «имеет-а».
Но на практике, как мы видим, наследование иногда не лучший подход. Просто в нашем примере водитель — сотрудник, и менеджер — тоже сотрудник. Но когда наша задача состоит в том, чтобы объединить разные части в один элемент, как здесь, композиция действительно может быть намного лучше, чем наследство.
В конце этой статьи я хочу подчеркнуть, что и наследование, и композиция являются хорошими подходами, когда вы используете их в нужном месте, когда они должны быть. В одном случае композиция лучше наследования, а в другом наоборот.
И, конечно же, мы можем объединить наследование и композицию вместе, если у нас есть отношение «есть-а», но мы хотим добавить разные значения или методы, мы можем иметь некоторый базовый класс, который дает все общие функции для наших экземпляров, и мы также можем использовать композицию для добавления других, конкретных функций.
Оригинал