Можем ли мы полностью заменить наследование, используя шаблон стратегии и внедрение зависимостей?

10

Например:

var duckBehaviors = new Duckbehavior();
duckBehaviors.quackBehavior = new Quack();
duckBehaviors.flyBehavior = new FlyWithWings();
Duck mallardDuck = new Duck(DuckTypes.MallardDuck, duckBehaviors)

Поскольку класс Duck содержит все типы поведения (абстрактные), создание нового класса MallardDuck(который расширяется Duck), по-видимому, не требуется.

Ссылка: Head First Design Pattern, глава 1.

Дипак Мишра
источник
Какие типы Duckbehavior.quackBehaviorи другие поля в вашем коде?
max630
В вашем примере нет внедрения зависимостей.
Дэвид Конрад
11
Наследование - это замечательно, когда вы являетесь разработчиком от младшего до среднего уровня, потому что существует долгая история рефакторинга трюков и шаблонов проектирования вокруг него. Но большинство опытных разработчиков, с которыми я говорил, предпочитают действительно мелкие иерархии наследования, основанные на композиции, когда это возможно. Наследование может вызвать более тесную связь, чем необходимо, и может усложнить изменения.
Марк Роджерс
5
@DavidConrad: ОП не занимается обновлением класса . DI / IoC о внедрении зависимостей, а не о контейнерах или о том, чтобы никогда не использовать «new»; это совершенно ортогональная проблема. Кроме того, вам всегда нужно где-то менять код - будь то корень композиции или какой-либо конфигурационный файл. Элемент управления инвертируется здесь в типе Duck, так как то, что контролирует создание зависимостей, это не Duck ctor, а некоторый внешний контекст; это пример игрушки, приведенный для ясности, прекрасно, что внешний контекст представлен просто вызывающим кодом.
Филипп Милованович

Ответы:

21

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

Шаблон стратегии позволяет изменять поведение во время выполнения под тем же интерфейсом. Я мог бы сказать крякве летать и смотреть, как она летит с крыльями. Тогда обменяйте это на утку пилота реактивного самолета и наблюдайте, как это летит с авиакомпаниями Дельты. Делать это во время работы программы - это образец стратегии.

Внедрение зависимостей - это метод, позволяющий избежать жесткого кодирования зависимостей, чтобы они могли меняться независимо, не требуя изменения клиентов при их изменении. Клиенты просто выражают свои потребности, не зная, как их встретят. Таким образом, как они встречаются, решается в другом месте (как правило, в основном). Вам не нужны две утки, чтобы использовать эту технику. Просто то, что использует утка, не зная и не заботясь, какая утка. Что-то, что не строит утку и не ищет ее, но совершенно рад использовать любую утку, которую вы ей подадите.

Если у меня есть конкретный класс утка, я могу реализовать его поведение мухи. Я мог бы даже заставить его переключать поведение с полета с крыльями на полет с дельтой, основываясь на переменной состояния. Эта переменная может быть логическое значение, INT, или это может быть , FlyBehaviorчто есть flyметод , который делает все , что летающий стиль без меня, чтобы проверить его с если. Теперь я могу менять стиль полета, не меняя типы уток. Теперь кряквы могут стать летчиками. Это состав и делегирование . Утка состоит из поведения FlyBehavior и может передавать ему запросы на полет. Таким образом, вы можете заменить все свое поведение утки одновременно или оставить что-то для каждого поведения или любой комбинации между ними.

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

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

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

Хотя готовность снести вещи позже может освободить вас от чрезмерного проектирования, есть что-то очень мощное в способности спроектировать то, что использует утку, без необходимости знать, что на самом деле будет делать утка при использовании. Это незнание - сильная вещь. Это позволяет вам на некоторое время перестать думать об утках и подумать об остальном коде.

«Можем ли мы» и «должны ли мы» - это разные вопросы. Композиция Favor Over Inheritance не говорит, что никогда не используйте наследование. Есть еще случаи, когда наследование имеет наибольшее значение. Я покажу вам мой любимый пример :

public class LoginFailure : System.ApplicationException {}

Наследование позволяет создавать исключения с более конкретными описательными именами в одной строке.

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

candied_orange
источник
1
« Никогда не стоит недооценивать ценность доброго имени ». Время новой одежды Императора: LoginExceptionне хорошее имя. Это классическое «smurf naming», и, если я уберу Exception, все, что у меня есть Login, это ничего не говорит мне о том, что пошло не так.
Дэвид Арно
К сожалению, мы застряли с нелепым соглашением Exceptionоб окончании каждого класса исключений, но, пожалуйста, не путайте «застрял с этим» с «хорошим».
Дэвид Арно
@DavidArno Если вы можете предложить лучшее имя, я все уши. Здесь мы пользуемся тем, что не попадаем в соглашения существующей кодовой базы. Если бы у вас была возможность изменить мир одним движением пальцев, что бы вы назвали?
candied_orange
Я бы назвал их, StackOverflow, OutOfMemory, NullReferenceAccess, и LoginFailureт.д. В принципе, принимать «Exception» от имени. И, если необходимо, исправьте их так, чтобы они описывали, что пошло не так.
Дэвид Арно
@DavidArno по вашей команде.
candied_orange
7

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

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

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

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

Мартин Маат
источник
-1

Наследование не так важно, как когда-то думали. Это все еще важно , и удаление это было бы плохой ошибкой.

Крайним примером является Objective-C, где все является подклассом NSObject (компилятор фактически не позволяет вам объявить класс, у которого нет базового класса, поэтому все, что не встроено в компилятор, должно быть подклассом чего-то встроен в компилятор). В NSObject встроено множество полезных вещей, которые вам не придется упускать.

Другой интересный пример - UIView, базовый класс «view» в UIKit для разработки под iOS. Это класс, который с одной стороны немного похож на абстрактный класс, объявляющий функциональность, которую должны заполнять подклассы, но он также полезен сам по себе. У него есть подклассы, предоставляемые UIKit, которые часто используются как есть. Композиция, разработанная разработчиком, устанавливает подпредставления в виде. И есть часто определяемые разработчиком подклассы, которые часто используют композицию. Нет строгого единого правила или даже правил, вы используете то, что лучше всего соответствует вашим требованиям.

gnasher729
источник
Ваш «экстремальный пример» примерно таков, как функционирует большинство современных ОО-языков: подклассы Python type, все не примитивы в Java наследуются Object, у всего в JavaScript есть прототип, заканчивающийся Objectи т. Д.
Delioth
-1

[Я рискну нахальный ответ на главный вопрос.]

Можем ли мы полностью заменить наследование, используя шаблон стратегии и внедрение зависимостей?

Да ... кроме того, что сам шаблон стратегии использует наследование. Шаблон стратегии работает на наследование интерфейса. Замена наследования на состав + стратегия перемещает наследование в другое место. Тем не менее, такая замена часто стоит того, потому что она позволяет разделить иерархии .

Ник Алексеев
источник
2
« Узор стратегия работает на наследование интерфейса ». Помните, что шаблон стратегии - это шаблон дизайна; не шаблон реализации. Таким образом, он может быть реализован с использованием интерфейсов, но он также может быть реализован с использованием, например, хеш / словарь функций.
Дэвид Арно
3
Наследование интерфейса - это вовсе не наследование, это просто своего рода контракт или классификатор. Вы можете использовать делегатов также для стратегии (я предпочитаю использовать их).
Дипак Мишра
Плюс один, чувак: ... работает над наследованием интерфейса , слава о правильном использовании общего термина "интерфейс". Я думаю, что плохой или некомпетентный дизайн класса является основной причиной, по которой возникает этот вопрос. Объяснить, почему / как бы взять книгу; Дьявол кроется в деталях. Я работал с проектами наследования, которые было радостно созерцать, и в то же время глубокое наследство было адом. Я видел дурацкий interfaceдизайн, исправленный по наследству. Скрытая проблема здесь заключается в несовместимом поведении, зависящем от изменения поведения. P, S. наследование и интерфейс не являются взаимоисключающими,
radarbob
@DavidArno comment : Этот комментарий будет хорошо, если он будет добавлен к OP-вопросу. Вопрос ОП технически неверно сформулирован, но мы все еще получаем его. Вопрос не в этом конкретном ответе.
Радар Боб
@DeepakMishra комментарий: . «Интерфейс» - это все открытые члены класса. К сожалению, значение «интерфейс» перегружено. Мы должны быть осторожны, чтобы различать общее значение с ключевым словом языка программированияinterface
radarbob
-2

Нет. Если утке кряквы требуются другие параметры для создания экземпляра, чем у утки какого-либо другого типа, это было бы кошмаром для изменения. Также вы должны быть в состоянии создать утку? Это скорее утка кряква, утка мандаринка или все остальные виды уток.

Также мне может потребоваться некоторая логика вокруг типов, чтобы иерархия типов была лучше.

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

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

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

Бенджамин Эриксен
источник