¿Por qué debería usar una clase de fábrica en lugar de la construcción directa de objetos?

141

He visto la historia de varios proyectos de biblioteca de clases С # y Java en GitHub y CodePlex, y veo una tendencia a cambiar a las clases de fábrica en lugar de a la creación de instancias directa de objetos.

¿Por qué debo usar clases de fábrica extensivamente? Tengo una biblioteca bastante buena, donde los objetos se crean a la antigua usanza, invocando constructores públicos de clases. En los últimos compromisos, los autores cambiaron rápidamente todos los constructores públicos de miles de clases a internos, y también crearon una enorme clase de fábrica con miles de métodos CreateXXX static que solo devuelven objetos nuevos llamando a los constructores internos de las clases. El API externo del proyecto está roto, bien hecho.

¿Por qué sería útil tal cambio? ¿Cuál es el punto de refactorización de esta manera? ¿Cuáles son los beneficios de reemplazar llamadas a constructores de clases públicas con llamadas a métodos de fábrica estática?

¿Cuándo debo usar constructores públicos y cuándo debo usar fábricas?

    
pregunta rufanov 14.08.2014 - 05:27
fuente

9 respuestas

52

Las clases de fábrica a menudo se implementan porque permiten que el proyecto siga los principios de SOLID más de cerca. En particular, la segregación de interfaces y los principios de inversión de dependencia.

Las fábricas e interfaces permiten mucha más flexibilidad a largo plazo. Permite un diseño más desacoplado y, por lo tanto, más comprobable. Aquí hay una lista no exhaustiva de por qué puede seguir este camino:

  • Le permite introducir un Inversión de control (IoC) contenedor fácilmente
  • Hace que su código sea más comprobable, ya que puede simular interfaces
  • Le brinda mucha más flexibilidad cuando llega el momento de cambiar la aplicación (es decir, puede crear nuevas implementaciones sin cambiar el código dependiente)

Considera esta situación.

El ensamblaje A (- > significa depende de):

Class A -> Class B
Class A -> Class C
Class B -> Class D

Quiero mover la Clase B a la Asamblea B, que depende de la Asamblea A. Con estas dependencias concretas, tengo que mover la mayor parte de toda la jerarquía de clases a través. Si uso interfaces, puedo evitar gran parte del dolor.

Asamblea A:

Class A -> Interface IB
Class A -> Interface IC
Class B -> Interface IB
Class C -> Interface IC
Class B -> Interface ID
Class D -> Interface ID

Ahora puedo mover la clase B al ensamblaje B sin ningún tipo de dolor. Todavía depende de las interfaces en el ensamblaje A.

El uso de un contenedor IoC para resolver sus dependencias le permite aún más flexibilidad. No es necesario actualizar cada llamada al constructor siempre que cambie las dependencias de la clase.

Seguir el principio de segregación de interfaz y el principio de inversión de dependencia nos permite crear aplicaciones altamente flexibles y desacopladas. Una vez que haya trabajado en uno de estos tipos de aplicaciones, nunca más querrá volver a usar la palabra clave new .

    
respondido por el Stephen 14.08.2014 - 05:51
fuente
111
Como dijo whatsisname , creo que este es el caso del diseño de software cargo sect . Las fábricas, especialmente las de tipo abstracto, solo se pueden utilizar cuando su módulo crea varias instancias de una clase y usted quiere que el usuario de este módulo pueda especificar qué tipo de clase crear. Este requisito es bastante raro, ya que la mayoría de las veces solo necesita una instancia y puede pasarla directamente en lugar de crear una fábrica explícita.

La cuestión es que las fábricas (y los singletons) son extremadamente fáciles de implementar y por eso la gente las usa mucho, incluso en lugares donde no son necesarias. Entonces, cuando el programador piensa "¿Qué patrones de diseño debo usar en este código?" La fábrica es lo primero que le viene a la mente.

Muchas fábricas se crean porque "Tal vez, un día, tendré que crear esas clases de manera diferente" en mente. Lo que es una clara violación de YAGNI .

Y las fábricas se vuelven obsoletas cuando introduces el marco IoC, porque IoC es solo un tipo de fábrica. Y muchos marcos IoC pueden crear implementaciones de fábricas específicas.

Además, no hay ningún patrón de diseño que diga crear enormes clases estáticas con los métodos CreateXXX que solo llaman constructores. Y especialmente no se llama Fábrica (ni Fábrica Abstracta).

    
respondido por el Euphoric 14.08.2014 - 08:45
fuente
66

La moda de patrones de Factory se deriva de una creencia casi dogmática entre los codificadores en lenguajes de "estilo C" (C / C ++, C #, Java) de que el uso de la palabra clave "new" es malo y debe evitarse a toda costa (o al menos centralizado). Esto, a su vez, proviene de una interpretación ultra estricta del Principio de Responsabilidad Única (la "S" de SOLID), y también del Principio de Inversión de Dependencia (la "D"). Dicho de manera simple, el SRP dice que, idealmente, un objeto de código debería tener una "razón para cambiar", y una única; ese "motivo para cambiar" es el propósito central de ese objeto, su "responsabilidad" en el código base, y cualquier otra cosa que requiera un cambio en el código no debería requerir la apertura de ese archivo de clase. El DIP es aún más simple; un objeto de código nunca debe depender de otro objeto concreto, sino de una abstracción.

Caso en cuestión, al utilizar "nuevo" y un constructor público, está acoplando el código de llamada a un método de construcción específico de una clase concreta específica. Su código ahora debe saber que existe una clase MyFooObject y tiene un constructor que toma una cadena y un int. Si ese constructor alguna vez necesita más información, todos los usos del constructor deben actualizarse para pasar esa información, incluida la que está escribiendo ahora, y por lo tanto, deben tener algo válido para transmitir, y por lo tanto deben tener o cambiarlo para obtenerlo (agregar más responsabilidades a los objetos consumidores). Además, si MyFooObject se reemplaza alguna vez en el código base por BetterFooObject, todos los usos de la clase antigua deben cambiarse para construir el nuevo objeto en lugar del antiguo.

Entonces, en cambio, todos los consumidores de MyFooObject deben depender directamente de "IFooObject", que define el comportamiento de las clases de implementación, incluido MyFooObject. Ahora, los consumidores de IFooObjects no pueden simplemente construir un IFooObject (sin tener el conocimiento de que una clase concreta particular es un IFooObject, que no necesitan), por lo tanto, se les debe dar una instancia de una clase o método de implementación de IFooObject desde el exterior, por otro objeto que tiene la responsabilidad de saber cómo crear el objeto IFooObject correcto para la circunstancia, que en nuestro lenguaje generalmente se conoce como una Fábrica.

Ahora, aquí es donde la teoría se encuentra con la realidad; un objeto no puede nunca estar cerrado a todos los tipos de cambio todo el tiempo. En este caso, IFooObject es ahora un objeto de código adicional en el código base, que debe cambiar cada vez que cambie la interfaz requerida por los consumidores o las implementaciones de IFooObjects. Eso introduce un nuevo nivel de complejidad involucrado en cambiar la forma en que los objetos interactúan entre sí a través de esta abstracción. Además, los consumidores todavía tendrán que cambiar, y más profundamente, si la interfaz en sí es reemplazada por una nueva.

Un buen programador sabe cómo equilibrar YAGNI ("No lo vas a necesitar") con SOLID, analizando el diseño y encontrando lugares que es muy probable que tengan que cambiar de una manera particular, y refactorizando para que sean más tolerante a ese tipo de cambio, porque en ese caso "usted es lo va a necesitar".

    
respondido por el KeithS 14.08.2014 - 18:13
fuente
29

Los constructores están bien cuando contienen código corto y simple.

Cuando la inicialización es más que asignar algunas variables a los campos, una fábrica tiene sentido. Estos son algunos de los beneficios:

  • El código largo y complicado tiene más sentido en una clase dedicada (una fábrica). Si el mismo código se coloca en un constructor que llama a un montón de métodos estáticos, esto contaminará la clase principal.

  • En algunos idiomas y en algunos casos, lanzar excepciones en los constructores es una muy mala idea , ya que puede introducir errores.

  • Cuando invocas a un constructor, tú, la persona que llama, necesita saber el tipo exacto de la instancia que quieres crear. No siempre es así (como Feeder , solo necesito construir Animal para alimentarlo; no me importa si es Dog o Cat ).

respondido por el Arseni Mourzenko 14.08.2014 - 05:40
fuente
9

Si trabaja con interfaces, puede permanecer independiente de la implementación real. Se puede configurar una fábrica (a través de propiedades, parámetros o algún otro método) para crear una instancia de varias implementaciones diferentes.

Un ejemplo sencillo: desea comunicarse con un dispositivo pero no sabe si será a través de Ethernet, COM o USB. Se define una interfaz y 3 implementaciones. En el tiempo de ejecución, puede seleccionar el método que desea y la fábrica le dará la implementación adecuada.

Úsalo a menudo ...

    
respondido por el paul 14.08.2014 - 07:41
fuente
6

Es un síntoma de una limitación en los sistemas de módulos de Java / C #.

En principio, no hay razón para que no puedas cambiar una implementación de una clase por otra con las mismas firmas de constructores y métodos. Hay idiomas que permiten esto. Sin embargo, Java y C # insisten en que cada clase tiene un identificador único (el nombre completo) y el código del cliente termina con una dependencia codificada.

Puede ordenar sortear esto jugando con el sistema de archivos y las opciones del compilador para que com.example.Foo se asigne a un archivo diferente, pero esto es sorprendente e intuitivo. Incluso si lo hace, su código está todavía vinculado a una sola implementación de la clase. Es decir. Si escribe una clase Foo que depende de una clase MySet , puede elegir una implementación de MySet en el momento de la compilación, pero aún no puede crear una instancia de Foo s usando dos implementaciones diferentes de MySet .

Esta desafortunada decisión de diseño obliga a las personas a usar interface s innecesariamente para probar su código en el futuro contra la posibilidad de que más tarde necesitarán una implementación diferente de algo, o para facilitar las pruebas unitarias. Esto no siempre es factible; Si tiene algún método que analice los campos privados de dos instancias de la clase, no podrá implementarlos en una interfaz. Por eso, por ejemplo, no ves union en la interfaz Set de Java. Aún así, fuera de los tipos y colecciones numéricas, los métodos binarios no son comunes, por lo que normalmente puedes salirse con la suya.

Por supuesto, si llama a new Foo(...) todavía tiene una dependencia de la clase , por lo que necesita una fábrica si desea que una clase pueda crear una instancia de una interfaz directamente. Sin embargo, generalmente es una mejor idea aceptar la instancia en el constructor y dejar que otra persona decida qué implementación usar.

Depende de usted decidir si vale la pena inflar su código base con interfaces y fábricas. Por un lado, si la clase en cuestión es interna a su base de código, refactorizar el código para que use una clase diferente o una interfaz en el futuro es trivial; puede invocar YAGNI y refactorizar más tarde si surge la situación. Pero si la clase es parte de la API pública de una biblioteca que ha publicado, no tiene la opción de corregir el código del cliente. Si no usas interface y luego necesitas múltiples implementaciones, estarás atrapado entre una roca y un lugar difícil.

    
respondido por el Doval 14.08.2014 - 13:54
fuente
2

En mi opinión, solo están usando Simple Factory, que no es un patrón de diseño adecuado y no debe confundirse con Abstract Factory o Factory Method.

Y ya que han creado una "clase de tejido enorme con miles de métodos estáticos CreateXXX", eso suena a un anti-patrón (¿una clase de Dios, tal vez?).

Creo que los métodos Simple Factory y los creadores estáticos (que no requieren una clase externa) pueden ser útiles en algunos casos. Por ejemplo, cuando la construcción de un objeto requiere varios pasos, como la creación de instancias de otros objetos (por ejemplo, favoreciendo la composición).

No llamaría ni siquiera a eso una Fábrica, sino solo un conjunto de métodos encapsulados en alguna clase aleatoria con el sufijo "Fábrica".

    
respondido por el FranMowinckel 14.08.2014 - 14:09
fuente
1

Como usuario de una biblioteca, si la biblioteca tiene métodos de fábrica, debe usarlos. Usted asumiría que el método de fábrica le da al autor de la biblioteca la flexibilidad para realizar ciertos cambios sin afectar su código. Por ejemplo, podrían devolver una instancia de alguna subclase en un método de fábrica, que no funcionaría con un constructor simple.

Como creador de una biblioteca, usaría métodos de fábrica si quiere usar esa flexibilidad usted mismo.

En el caso que describe, parece tener la impresión de que reemplazar a los constructores con métodos de fábrica no tenía sentido. Sin duda fue un dolor para todos los involucrados; una biblioteca no debería eliminar nada de su API sin una buena razón. Entonces, si hubiera agregado métodos de fábrica, habría dejado disponibles los constructores existentes, tal vez en desuso, hasta que un método de fábrica ya no llame a ese constructor y el código que usa el constructor simple funciona menos bien de lo que debería. . Su impresión bien podría ser correcta.

    
respondido por el gnasher729 29.02.2016 - 09:53
fuente
-4

Esto parece ser obsoleto en la era de Scala y la programación funcional. Una base sólida de funciones reemplaza a un millón de clases.

También debe tener en cuenta que el doble {{ de Java ya no funciona cuando se usa una fábrica, es decir,

someFunction(new someObject() {{
    setSomeParam(...);
    etc..
}})

Lo que te permite crear una clase anónima y personalizarla.

En el dilema del espacio de tiempo, el factor tiempo ahora se reduce tanto gracias a las CPU rápidas que la programación funcional que permite reducir el espacio, es decir, el tamaño del código, ahora es práctica.

    
respondido por el Marc 07.03.2016 - 21:41
fuente

Lea otras preguntas en las etiquetas