Заводской паттерн нарушает принцип Open / Closed?

14

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

Дизайн фабрики

ShapeFactory Design

Армон Сафай
источник
2
Какую «фабричную модель» вы имеете в виду? Обычно фабрика - это любой объект или метод, который служит для создания экземпляра объекта. Кроме того, существуют конкретные варианты этой общей идеи, такие как шаблон абстрактной фабрики, где каждый экземпляр фабрики представляет определенную палитру вариантов выбора - обычно управляемую с помощью подклассов, а не условий.
am
3
Спасибо за эту информацию, это многое проясняет. Это, безусловно, пример фабричного паттерна, но не абстрактный фабричный паттерн, обычно ассоциируемый с фабричными паттернами. Код в этой статье довольно сомнителен, и я не ожидал бы увидеть что-либо подобное в реальном коде.
am
@ArmonSafai: Вы часто связываете этот пост, но на самом деле не объясняете почему. Мы все как-то неосведомлены о закономерностях? У нас тоже есть Google, как и вы.
Роберт Харви
1
@RobertHarvey Я связываю этот пост в блоге, чтобы показать, как фабричный шаблон на этой странице использует условные
выражения

Ответы:

20

Обычная объектно-ориентированная мудрость - избегать ifоператоров и заменять их динамической диспетчеризацией переопределенных методов в подклассах абстрактного класса. Все идет нормально.

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

Следовательно, нет никакого способа, которым написание фабричного класса могло бы отказаться от ifутверждений. Это переместит бремя выбора определенного класса на вызывающего, что именно то, что шаблон должен избегать. Не все принципы являются абсолютными (фактически, ни один принцип не является абсолютным), и если вы используете этот шаблон, вы предполагаете, что выгода от него больше, чем выгода от неиспользования if.

Килиан Фот
источник
2
Вполне возможно создать фабричный образец без большого количества ifs. Посмотрите ответ @ BЈовић для простого примера того, как этого добиться. Downvoted.
Дэвид Арно
11
@DavidArno Естественно, есть разные способы выбора конкретного класса. Сервисный локатор - один, настраиваемый контейнер IoC - другой. Это просто детали реализации; они не отвлекают от основного сообщения Киллиана, заключающегося в том, что Фабрика освобождает вызывающего от необходимости решать, какой конкретный класс создавать. Не зацикливайтесь на деталях.
Роберт Харви
1
Потрясающее утверждение, которое никоим образом не отвечает на вопрос.
Мартин Маат
1
@ R.Schmitz Я думаю, что вы ошибаетесь в своем предположении. Я думаю, что многие пропустили этот вопрос из ОП: «Разве нам не нужно изменять ShapeFactory, если мы хотим добавить другие классы в будущем?» Ясно, что ОП сбит с толку, думая, что этот шаблон нарушает OCP, потому что для добавления новых функций вы должны изменить существующий код. Правильный ответ на этот вопрос можно найти в моем ответе. Краткий ответ: вы оставляете этот код в покое и применяете шаблон Abstract Factory, чтобы РАСШИРЯТЬ (не изменять) вашу существующую функциональность. Из-за этого ответ Килиана НЕ рассматривает вопрос.
hfontanez
5

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

Расширение условного добавления поддержки нового подкласса в будущем действительно будет, строго говоря, нарушением принципа открытого / закрытого. «Правильным» решением было бы создание новой фабрики с таким же интерфейсом. При этом соблюдение принципа O / C всегда следует сопоставлять с другими принципами проектирования, такими как KISS и YAGNI.

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

JacquesB
источник
Можете ли вы объяснить, как будет работать карта / конфигурация / реестр?
Armon Safai
@ArmonSafai: Вот пример: jkfill.com/2010/12/29/self-registering-factories-in-c-sharp
JacquesB
Самостоятельно регистрируемые фабрики, AFAIK, невозможны в C ++ внутри статических библиотек, поскольку неиспользуемые (то есть используемые odr) глобальные переменные отбрасываются цепями инструментов.
void.pointer
@ArmonSafai прочтите это, чтобы лучше понять goo.gl/RYuNSM
AZ_
2

Сам шаблон не нарушает принцип Open / Closed (OCP). Тем не менее, мы нарушаем OCP, когда мы используем шаблон неправильно.

Простой ответ на этот вопрос заключается в следующем:

  1. Создайте свою базовую функциональность, используя Factory Method Pattern.
  2. Расширьте свою функциональность, используя абстрактный шаблон фабрики

В приведенном примере базовая функциональность поддерживает три формы: круг, прямоугольник и квадрат. Предположим, что в будущем вам потребуется поддержка Triangle, Pentagon и Hexagon. Чтобы сделать это БЕЗ нарушения OCP, вы должны создать дополнительную фабрику для поддержки ваших новых фигур (давайте назовем ее AdvancedShapeFactory), а затем использовать AbstractFactory, чтобы решить, какую фабрику вам нужно создать, чтобы создавать любые нужные фигуры.

hfontanez
источник
Я очень предпочитаю решение для саморегистрации фабрик (при отсутствии действительно настраиваемого контейнера IoC, который является лучшим), поскольку в противном случае мы получаем из вашего предложения в основном фабрик фабрик , и именно тогда все становится слишком сложным.
Nom1fan
1

Если вы говорите о шаблоне абстрактной фабрики, то принятие решений часто не в самой фабрике, а в коде приложения. Именно этот код выбирает конкретную фабрику для создания экземпляра и передачи клиентскому коду, который будет использовать объекты, созданные фабрикой. Смотрите конец примера Java здесь: https://en.wikipedia.org/wiki/Abstract_factory_pattern

Принятие решения не обязательно подразумевает ifзаявления. Он может прочитать конкретный тип Factory из файла конфигурации, извлечь его из структуры карты и т. Д.

guillaume31
источник
Если я, вызывающая сторона, принимаю решение, какой конкретный класс создать, то почему я беспокоюсь об абстрактной фабрике?
Роберт Харви
Пожалуйста, определите «звонящий». Как я описываю в своем ответе, есть глобальный код приложения, а затем код, который должен порождать объекты с использованием Factory. В то время как последний действительно должен не знать о конкретном классе, чтобы создать экземпляр, некоторый другой контекстный код должен знать об этом и обновлять его ...
guillaume31
0

Если вы думаете об Open-Close на уровне класса с помощью этой фабрики, вы создаете другой класс в вашей системе Open-Close, например, если у вас есть другой класс, который принимает один Shape и вычисляет площадь (типичный пример), этот класс является OpenClose, потому что он может рассчитать площадь для новых типов фигур без изменений. Затем у вас есть другой класс, который рисует фигуру, другой класс, который принимает N фигур и возвращает больший, и вы можете думать в целом, что другие классы в вашей системе, которые работают с фигурами, являются Open-Close (по крайней мере, для фигур). Глядя на конструкцию, фабрика позволяет открывать-закрывать остальную часть системы и, конечно, сама фабрика НЕ ​​ОТКРЫВАЕТ-закрывает.

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

AlfredoCasado
источник
0

Принцип открытого-закрытого типа, как принцип подстановки Лискова, применяется к деревьям классов, к иерархиям наследования. В вашем примере фабричный класс не входит в семейное дерево классов, которые он создает, поэтому он не может нарушать эти правила. Было бы нарушение, если бы ваш GetShape (или, более точно, CreateShape) был реализован в базовом классе Shape.

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

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

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

BЈовић
источник
6
Как переключатель / случай лучше, чем условные выражения? Использование карты / dict / table для представления кода в качестве данных хорошо, если вам действительно нужен реестр различных реализаций - например, в некоторых реализациях контейнера DI. Но иметь разные обратные вызовы одного и того же типа не обязательно для большинства фабрик! Я не совсем понимаю, почему вы предлагаете это. Кроме того, многие DI-контейнеры реализованы с точки зрения фабричных объектов, поэтому предложение использовать DI вместо фабрик кажется несколько круглым.
am
1
@amon Я хотел использовать другие типы DI, а не заводские.
BЈовић
1
Как ваша фабрика решит, какой указатель использовать? В конце концов вы должны принять решение.
whatsisname
@ArmonSafai ???
BЈовић