¿El patrón de fábrica viola el principio abierto / cerrado?

14

¿Por qué este ShapeFactory utiliza declaraciones condicionales para determinar qué objeto instanciar? ¿No tenemos que modificar ShapeFactory si queremos agregar otras clases en el futuro? ¿Por qué esto no viola el principio abierto cerrado?

Diseño de patrones de fábrica

Diseño de fábrica

Armon Safai
fuente
2
¿A qué "patrón de fábrica" ​​se refiere precisamente? En general, una fábrica es cualquier objeto o método que sirve para instanciar un objeto. Luego, hay variaciones específicas de esta idea general, como el Patrón de fábrica abstracto, donde cada instancia de fábrica representa una paleta específica de opciones, generalmente administrada a través de subclases en lugar de condicionales.
amon
3
Gracias por esa información, esto se aclara mucho. Este es definitivamente un ejemplo de un patrón de fábrica, pero no del Patrón abstracto de fábrica comúnmente asociado con los patrones de fábrica. El código en ese artículo es bastante cuestionable, y no esperaría ver algo así en el código real.
amon
@ArmonSafai: Estás vinculando mucho esta publicación de blog, pero realmente no estás explicando por qué. ¿Somos todos de alguna manera ignorantes del patrón? También tenemos Google, como tú.
Robert Harvey
1
@RobertHarvey Estoy vinculando esta publicación de blog para mostrar cómo el patrón de fábrica en esa página está usando condicionales
Armon Safai

Respuestas:

20

La sabiduría convencional orientada a objetos es evitar ifenunciados y reemplazarlos con despacho dinámico de métodos anulados en subclases de una clase abstracta. Hasta aquí todo bien.

Pero el objetivo del patrón de fábrica es liberarlo de tener que conocer las subclases individuales y trabajar solo con la superclase abstracta . La idea es que la fábrica sepa mejor que usted qué clase específica crear, y será mejor que trabaje solo con los métodos publicados por la superclase. Esto es a menudo cierto y un patrón valioso.

Por lo tanto, no hay forma de que escribir una clase de fábrica pueda renunciar a las ifdeclaraciones. Cambiaría la carga de elegir una clase específica a la persona que llama, que es exactamente lo que se supone que debe evitar el patrón. No todos los principios son absolutos (de hecho, ningún principio es absoluto), y si usa este patrón, supondría que el beneficio es mayor que el beneficio de no usar un if.

Kilian Foth
fuente
2
Es perfectamente posible crear un patrón de fábrica sin muchos ifs. Vea la respuesta de @ BЈовић para un ejemplo simple de cómo lograr esto. Voto negativo
David Arno
11
@DavidArno Naturalmente, hay diferentes formas de elegir la clase concreta. Service Locator es uno, un contenedor de IoC configurable es otro. Esos son solo detalles de implementación; no restan valor al mensaje principal de Killian, que es que la Fábrica alivia a la persona que llama de tener que decidir qué clase concreta instanciar. No te dejes llevar por los detalles.
Robert Harvey
1
Excelente declaración que de ninguna manera responde a la pregunta.
Martin Maat
1
@ R.Schmitz Creo que eres incorrecto en tu suposición. Creo que mucha gente perdió esta pregunta del OP: "¿No tenemos que modificar ShapeFactory si queremos agregar otras clases en el futuro?" CLARAMENTE, el OP se confunde pensando que este patrón viola el OCP porque para agregar una nueva funcionalidad, debe modificar el código existente. La respuesta correcta a esta pregunta se puede encontrar en mi respuesta. La respuesta corta: deja ese código solo y aplica el patrón Abstract Factory para EXTENDER (no modificar) su funcionalidad existente. Debido a esto, la respuesta de Kilian NO aborda la pregunta.
hfontanez
5

El ejemplo probablemente usa una declaración condicional porque es la más simple. Una implementación más compleja podría usar un mapa o configuración o (si quieres ser realmente elegante) algún tipo de registro donde las clases puedan registrarse por sí mismas. Sin embargo, no hay nada de malo en usar un condicional si el número de clases es pequeño y cambia con poca frecuencia.

Extender el condicional para agregar soporte para una nueva subclase en el futuro sería, estrictamente hablando, una violación del principio abierto / cerrado. La solución "correcta" sería crear una nueva fábrica con la misma interfaz. Dicho esto, la adhesión al principio de O / C siempre debe compararse con otros principios de diseño como KISS y YAGNI.

Dicho esto, el código que se muestra es claramente un código de ejemplo que está diseñado para mostrar el concepto de fábrica y nada más. Por ejemplo, es realmente un mal estilo devolver nulo como lo hace el ejemplo, pero un manejo de errores más elaborado simplemente oscurecería el punto. El código de ejemplo no es un código de calidad de producción, ninguno no debería esperar que sea.

JacquesB
fuente
¿Puede explicar cómo funcionaría un mapa / configuración / registro?
Armon Safai
@ArmonSafai: Aquí hay un ejemplo: jkfill.com/2010/12/29/self-registering-factories-in-c-sharp
JacquesB
Las fábricas de autorregistro son, AFAIK, imposibles en C ++ dentro de las bibliotecas estáticas ya que las cadenas globales de herramientas descartan las variables globales no utilizadas (es decir, usadas por odr).
void.pointer
@ArmonSafai lee esto para comprender mejor goo.gl/RYuNSM
AZ_
2

El patrón en sí mismo no viola el Principio Abierto / Cerrado (OCP). Sin embargo, violamos el OCP cuando usamos el patrón incorrectamente.

La respuesta simple a esta pregunta es la siguiente:

  1. Cree su funcionalidad base utilizando el patrón de método de fábrica .
  2. AMPLÍE su funcionalidad usando el patrón abstracto de fábrica

En el ejemplo proporcionado, su funcionalidad base admite tres formas: Círculo, Rectángulo y Cuadrado. Suponga que necesita admitir Triángulo, Pentágono y Hexágono en el futuro. Para hacer esto SIN violar OCP, debe crear una fábrica adicional para admitir sus nuevas formas (llamemos AdvancedShapeFactory) y luego usar AbstractFactory para decidir qué fábrica necesita crear para crear las formas que necesita.

hfontanez
fuente
Prefiero la solución de fábricas de autorregistro (en ausencia de un verdadero contenedor de IoC configurable que sea lo mejor), ya que de lo contrario lo que obtenemos de su propuesta es básicamente fábricas de fábricas , y es cuando las cosas se vuelven demasiado complejas.
Nom1fan
1

Si está hablando del patrón Abstract Factory, la toma de decisiones a menudo no está en la Factory en sí, sino en el código de la aplicación. Es ese código el que elige qué fábrica de concreto instanciar y pasar al código del cliente que utilizará los objetos producidos por la Fábrica. Vea el final del ejemplo de Java aquí: https://en.wikipedia.org/wiki/Abstract_factory_pattern

La toma de decisiones no necesariamente implica ifdeclaraciones. Podría leer el tipo concreto de Factory de un archivo de configuración, derivarlo de una estructura de mapa, etc.

guillaume31
fuente
Si yo, la persona que llama, tomo la decisión de qué clase concreta instanciar, ¿por qué me estoy molestando con una Fábrica abstracta?
Robert Harvey
Defina "la persona que llama". Como describo en mi respuesta, está el código de aplicación global y luego está el código que necesita generar objetos usando una Fábrica. Mientras que este último efecto, hay que ser conscientes de la clase concreta para crear una instancia, algún otro código contextual tiene que saber sobre él y lo nuevo hasta que ...
guillaume31
0

Si piensa en Open-Close a nivel de clase con esta fábrica, está creando otra clase en su sistema Open-Close, por ejemplo, si tiene otra clase que toma una Forma y calcula el área (ejemplo típico), esta clase es OpenClose porque Puede calcular el área para nuevos tipos de formas sin modificación. Luego tiene otra clase que dibuja la forma, otra clase que toma N formas y devuelve la más grande y puede pensar en general que las otras clases en su sistema que tratan con formas son Open-Close (al menos sobre formas). Mirando el diseño, la fábrica permite que el resto del sistema sea Abierto-Cerrado y, por supuesto, la fábrica misma NO ES Abrir-Cerrar.

Por supuesto, también puede hacer que esta fábrica se abra-cierre, a través de algún tipo de carga dinámica y todo su sistema puede ser Abrir-Cerrar (puede agregar nuevas formas dejando caer un tarro en el classpath, por ejemplo). Debe evaluar si esta complejidad adicional vale la pena dependiendo del sistema que esté construyendo, no todos los sistemas necesitan características conectables y no todo el sistema debe estar completamente abierto-cerrado.

AlfredoCasado
fuente
0

El principio abierto-cerrado, como el principio de sustitución de Liskov, se aplica a los árboles de clase, a las jerarquías de herencia. En su ejemplo, la clase de fábrica no está en el árbol genealógico de las clases que instancia, por lo que no puede violar estas reglas. Habría una violación si su GetShape (o más acertadamente, CreateShape) se implementara en la clase base Shape.

Martin Maat
fuente
-2

Todo depende de cómo lo implemente. Puede usar std::mappara mantener punteros de función a funciones que crean objetos. Entonces no se viola el principio de apertura / cierre. O interruptor / caja.

De todos modos, si no le gusta el patrón de fábrica, siempre puede usar la inyección de dependencia.

BЈовић
fuente
66
¿Cómo es mejor el interruptor / caja que los condicionales? Usar un map / dict / table para representar el código como datos es bueno, si realmente necesita un registro de diferentes implementaciones, por ejemplo, en algunas implementaciones de contenedores DI. ¡Pero no es necesario tener diferentes devoluciones de llamada del mismo tipo para la mayoría de las fábricas! No entiendo por qué estás sugiriendo eso. Además, muchos contenedores DI se implementan en términos de objetos de fábrica, por lo que sugerir utilizar DI en lugar de fábricas parece un poco circular.
amon
1
@amon Quise usar otros tipos de DI, no la fábrica.
B 17овић
1
¿Cómo va a decidir su fábrica qué puntero usar? Eventualmente tienes que tomar una decisión.
cuál es el
@ArmonSafai ???
B 18овић