¿Cómo evitaría otro lenguaje popular tener que usar el patrón de fábrica mientras se maneja una complejidad similar a la de Java / Java EE?

23

El patrón de fábrica (o al menos el uso de FactoryFactory..) es el blanco de muchos chistes, como aquí .

Además de tener nombres detallados y "creativos" como RequestProcessorFactoryFactory.RequestProcessorFactory , ¿hay algo fundamentalmente incorrecto con el patrón de fábrica si tiene que programar en Java / C ++ y hay un caso de uso para Abstract_factory_pattern ?

¿Cómo podría otro lenguaje popular (por ejemplo, Ruby o Scala ) evitar tener que usarlo mientras se maneja una complejidad similar?

La razón por la que pregunto es que veo principalmente las críticas a las fábricas mencionadas en el contexto del ecosistema Java / Java EE , pero nunca explican cómo otros lenguajes / marcos los resuelven.

senseiwu
fuente
44
El problema no es el uso de fábricas, es un patrón perfectamente fino. El uso excesivo de fábricas es especialmente frecuente en el mundo empresarial de Java.
CodesInChaos
Muy buen enlace de broma. Me encantaría ver una interpretación de lenguaje funcional para recuperar las herramientas para construir un estante de especias. Eso realmente lo llevaría a casa, creo.
Patrick M

Respuestas:

28

Su pregunta está etiquetada con "Java", no sorprende que se pregunte por qué se está burlando del patrón Factory: Java en sí viene con un abuso bien empaquetado de ese patrón.

Por ejemplo, intente cargar un documento XML desde un archivo y ejecute una consulta XPath en él. Necesita algo como 10 líneas de código solo para configurar las fábricas y constructores:

DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder(); 

Document xmlDocument = builder.parse(new FileInputStream("c:\\employees.xml"));
XPath xPath =  XPathFactory.newInstance().newXPath();

xPath.compile(expression).evaluate(xmlDocument);

Me pregunto si los chicos que diseñaron esta API alguna vez trabajaron como desarrolladores o simplemente leyeron libros y arrojaron cosas. Entiendo que simplemente no querían escribir un analizador y dejaron la tarea a otros, pero aún así es una implementación fea.

Como se pregunta cuál es la alternativa, aquí está cargando el archivo XML en C #:

XDocument xml = XDocument.Load("c:\\employees.xml");
var nodes = xml.XPathSelectElements(expression);

Sospecho que los desarrolladores de Java recién nacidos ven la locura de Factory y piensan que está bien hacerlo, si los genios que crearon Java los han usado tanto.

Factory y cualquier otro patrón son herramientas, cada una adecuada para un trabajo en particular. Si los aplica a trabajos que no son adecuados para usted, seguramente tendrá un código feo.

Sten Petrov
fuente
1
Solo una nota de que su alternativa C # está utilizando el LINQ to XML más nuevo: la alternativa DOM más antigua se habría visto así: XmlDocument xml = new XmlDocument(); xml.Load("c:\\employees.xml"); XmlNodeList nodes = xml.SelectNodes(expression); (En general, LINQ to XML es mucho más fácil de usar, aunque principalmente en otras áreas). Y aquí hay un artículo de KB Encontré, con un método recomendado por MS: support.microsoft.com/kb/308333
Bob
36

Como a menudo, las personas no entienden lo que está sucediendo (y eso incluye muchas de las risas).
No es el patrón de fábrica per se lo que es malo tanto como la forma en que muchas personas (tal vez incluso la mayoría) lo usan.

Y eso sin duda proviene de la forma en que se enseña la programación (y los patrones). A los alumnos (a menudo llamándose a sí mismos "estudiantes") se les dice que "creen X usando el patrón Y" y después de algunas iteraciones de eso piensan que esa es la forma de abordar cualquier problema de programación.
Entonces comienzan a aplicar un patrón específico que les gusta en la escuela contra cualquier cosa, sea apropiado o no.

Y eso incluye a profesores universitarios que escriben libros sobre diseño de software, lamentablemente.
La culminación de esto fue un sistema que tuve el disgusto de tener que mantener creado por uno de ellos (el hombre incluso tenía varios libros sobre diseño orientado a objetos para su nombre, y un puesto de profesor en el departamento de informática de una universidad importante )
Se basó en un patrón de 3 niveles, con cada nivel en sí un sistema de 3 niveles (tiene que desacoplar ...). En la interfaz entre cada conjunto de niveles, en ambos lados, había una fábrica para producir los objetos para transferir datos al otro nivel, y una fábrica para producir el objeto para traducir el objeto recibido a uno que pertenece a la capa receptora.
Para cada fábrica había una fábrica abstracta (quién sabe, la fábrica podría tener que cambiar y luego no querrá que cambie el código de llamada ...).
Y todo ese desastre fue, por supuesto, completamente indocumentado.

El sistema tenía una base de datos normalizada a la 5ta forma normal (no es broma).

Un sistema que era esencialmente poco más que una libreta de direcciones que podía usarse para registrar y rastrear documentos entrantes y salientes, tenía una base de código de 100 MB en C ++ y más de 50 tablas de bases de datos. La impresión de 500 cartas formales tomaría hasta 72 horas con una impresora conectada directamente a la computadora que ejecuta el software.

Es por eso que las personas se burlan de los patrones, y específicamente las personas que se concentran de manera decidida en un solo patrón específico.

jwenting
fuente
40
La otra razón es que algunos de los patrones de Gang of Four son trucos realmente detallados para cosas que se pueden hacer trivialmente en un lenguaje con funciones de primera clase, lo que los convierte en antipatrones inevitables. Los patrones "Estrategia", "Observador", "Fábrica", "Comando" y "Método de plantilla" son hacks para pasar funciones como argumentos; "Visitante" es un truco para realizar una coincidencia de patrones en una unión de tipo / variante / etiqueta de suma. El hecho de que algunas personas adoren mucho sobre algo que es literalmente trivial en muchos otros lenguajes es lo que lleva a las personas a burlarse de C ++, Java y lenguajes similares.
Doval
77
Gracias por esta anécdota. ¿Puedes también elaborar un poco sobre "cuáles son las alternativas"? parte de la pregunta para que no nos volvamos así también?
Philipp
1
@Doval ¿Puede decirme cómo devuelve el valor del comando ejecutado o la estrategia llamada cuando solo puede pasar la función? Y si dice cierres, tenga en cuenta que es exactamente lo mismo que crear una clase, excepto que es más explícito.
Eufórico
2
@Euphoric No veo el problema, ¿puedes explicarlo? En cualquier caso, no son exactamente lo mismo. También podría decir que un objeto con un solo campo es exactamente lo mismo que un puntero / referencia a una variable o que un número entero es exactamente lo mismo que una enumeración (real). Hay diferencias en la práctica: no tiene sentido comparar funciones; Todavía tengo que ver una sintaxis concisa para las clases anónimas; y todas las funciones con el mismo argumento y tipos de retorno tienen el mismo tipo (a diferencia de las clases / interfaces con diferentes nombres, que son diferentes incluso cuando son idénticos).
Doval
2
@Euphoric Correct. Eso se consideraría un mal estilo funcional si hay una manera de hacer el trabajo sin efectos secundarios. Una alternativa simple sería usar un tipo de suma / unión etiquetada para devolver uno de los N tipos de valores. En cualquier caso, supongamos que no hay una forma práctica; No es lo mismo que una clase. En realidad, es una interfaz (en el sentido OOP). No es difícil ver si considera que cualquier par de funciones con las mismas firmas sería intercambiable, mientras que dos clases nunca son intercambiables, incluso si tienen exactamente el mismo contenido.
Doval
17

Las fábricas tienen muchas ventajas que permiten diseños de aplicaciones elegantes en algunas situaciones. Una es que puede establecer las propiedades de los objetos que luego desea crear en un lugar creando una fábrica, y luego entregar esa fábrica. Pero a menudo no necesitas hacer eso. En ese caso, usar una Fábrica simplemente agrega complejidad adicional sin realmente darle nada a cambio. Tomemos esta fábrica, por ejemplo:

WidgetFactory redWidgetFactory = new ColoredWidgetFactory(COLOR_RED);
Widget widget = redWidgetFactory.create();

Una alternativa al patrón Factory es el patrón Builder muy similar. La principal diferencia es que las propiedades de los objetos creados por una Fábrica se establecen cuando se inicializa la Fábrica, mientras que un Generador se inicializa con un estado predeterminado y todas las propiedades se establecen después.

WidgetBuilder widgetBuilder = new WidgetBuilder();
widgetBuilder.setColor(COLOR_RED);
Widget widget = widgetBuilder.create();

Pero cuando la sobre ingeniería es su problema, reemplazar una Fábrica con un Constructor probablemente no sea una gran mejora.

El reemplazo más simple para cualquiera de los patrones es, por supuesto, crear instancias de objetos con un constructor simple con el newoperador:

Widget widget = new ColoredWidget(COLOR_RED);

Sin embargo, los constructores tienen un inconveniente crucial en la mayoría de los lenguajes orientados a objetos: deben devolver un objeto de esa clase exacta y no pueden devolver un subtipo.

Cuando necesite elegir el subtipo en tiempo de ejecución pero no desee recurrir a la creación de una clase Builder o Factory completamente nueva para eso, puede utilizar un método factory en su lugar. Este es un método estático de una clase que devuelve una nueva instancia de esa clase o una de sus subclases. Una Fábrica que no mantiene ningún estado interno a menudo se puede reemplazar con un método de fábrica de este tipo:

 Widget widget = Widget.createColoredWidget(COLOR_RED); // returns an object of class RedColoredWidget

Una nueva característica en Java 8 son las referencias de métodos que le permiten pasar métodos, tal como lo haría con una fábrica sin estado. Convenientemente, cualquier cosa que acepte una referencia de método también acepta cualquier objeto que implemente la misma interfaz funcional, que también puede ser una Fábrica completa con estado interno, por lo que puede introducir fácilmente fábricas más tarde, cuando vea una razón para hacerlo.

Philipp
fuente
2
Una fábrica a menudo también se puede configurar después de la creación. La principal diferencia, afaik, para un constructor es que un constructor generalmente se usa para crear una instancia única y compleja, mientras que las fábricas se usan para crear muchas instancias similares.
Cefalópodo
1
gracias (+1), pero la respuesta no explica cómo otros PL lo resolverían, sin embargo, gracias por señalar las referencias del método Java 8.
senseiwu
1
A menudo he pensado que tendría sentido en un marco orientado a objetos limitar la construcción real de objetos al tipo en cuestión, y foo = new Bar(23);ser equivalente a foo = Bar._createInstance(23);. Un objeto debería poder consultar de manera confiable su propio tipo utilizando un getRealType()método privado , pero los objetos deberían poder designar un supertipo para ser devuelto por llamadas externas getType(). El código externo no debería importar si una llamada a new String("A")realmente devuelve una instancia de String[en lugar de, por ejemplo, una instancia de SingleCharacterString].
supercat
1
Uso muchas de las fábricas de métodos estáticos cuando necesito elegir una subclase basada en el contexto, pero las diferencias deben ser opacas para la persona que llama. Por ejemplo, tengo una serie de clases de imágenes que contienen draw(OutputStream out)pero generan html ligeramente diferentes, la fábrica de métodos estáticos crea la clase adecuada para la situación y luego la persona que llama puede usar el método de dibujo.
Michael Shopsin
1
@supercat te puede interesar echar un vistazo al objetivo-c. Su construcción de objetos consiste en llamadas a métodos simples, generalmente [[SomeClass alloc] init]. Es posible devolver una instancia de clase completamente diferente u otro objeto (como un valor en caché)
axelarge
3

Las fábricas de uno u otro tipo se encuentran en prácticamente cualquier lenguaje orientado a objetos en circunstancias apropiadas. A veces, simplemente necesita una forma de seleccionar qué tipo de objeto crear basándose en un parámetro simple como una cadena.

Algunas personas lo llevan demasiado lejos e intentan diseñar su código para nunca tener que llamar a un constructor, excepto dentro de una fábrica. Las cosas comienzan a ponerse ridículas cuando tienes fábricas de fábrica.

Lo que me sorprendió cuando aprendí Scala, y no conozco a Ruby, pero creo que es de la misma manera, es que el lenguaje es lo suficientemente expresivo como para que los programadores no siempre estén tratando de llevar el trabajo de "plomería" a los archivos de configuración externos. . En lugar de sentirse tentado a crear fábricas de fábrica para conectar sus clases en diferentes configuraciones, en Scala utiliza objetos con rasgos mezclados. También es relativamente fácil crear DSLs simples en lenguaje para tareas que a menudo están sobredimensionadas en Java.

Además, como han señalado otros comentarios y respuestas, los cierres y las funciones de primera clase evitan la necesidad de muchos patrones. Por esa razón, creo que muchos de los antipatrones comenzarán a desaparecer a medida que C ++ 11 y Java 8 obtengan una adopción generalizada.

Karl Bielefeldt
fuente
gracias (+1) Su respuesta explica algunas de mis dudas sobre cómo Scala lo evitaría. Honestamente, ¿no crees que una vez (si) Scala se vuelve al menos la mitad de popular que Java a nivel empresarial, aparecerían ejércitos de marcos, patrones, servidores de aplicaciones pagas, etc.
senseiwu
Creo que si Scala superara a Java, sería porque la gente prefiere la "forma Scala" de arquitectura de software. Lo que me parece más probable es que Java continúe adoptando más de las características que llevan a las personas a cambiar a Scala, como los genéricos de Java 5 y los lambdas y streams de Java 8, que con suerte eventualmente resultarán en que los programadores abandonen los antipatrones que surgieron cuando esos características no estaban disponibles. Siempre habrá seguidores de FactoryFactory, sin importar cuán buenos sean los idiomas. Obviamente, a algunas personas les gustan esas arquitecturas, o no serían tan comunes.
Karl Bielefeldt
2

En general, existe la tendencia de que los programas de Java están terriblemente sobre-diseñados [cita requerida]. Tener muchas fábricas es uno de los síntomas más comunes de la sobre ingeniería; Por eso la gente se burla de ellos.

En particular, el problema que Java tiene con las fábricas es que en Java a) los constructores no son funciones yb) las funciones no son ciudadanos de primera clase.

Imagina que pudieras escribir algo como esto (deja que Functionsea ​​una interfaz bien conocida del JRE )

// Framework code
public Node buildTree(Function<BranchNode, Node, Node> branchNodeFactory) {
    Node current = nextNode();
    while (hasNextNode()) {
        current = branchNodeFactory.call(current, nextNode());
    }
    return current
}

// MyDataNode.java
public class MyBranchNode implements BranchNode {

    public MyBranchNode(Node left, Node right) { ... }
}

// Client code
Node root = buildTree(MyBranchNode::new);

Ver, no hay interfaces de fábrica o clases. Muchos lenguajes dinámicos tienen funciones de primera clase. Por desgracia, esto no es posible con Java 7 o anterior (implementar el mismo con las fábricas se deja como un ejercicio para el lector).

Por lo tanto, la alternativa no es tanto "usar un patrón diferente", sino más bien "usar un lenguaje con un mejor modelo de objeto" o "no sobre-diseñar problemas simples".

Cefalópodo
fuente
2
Esto ni siquiera intenta responder a la pregunta: "¿Cuáles son las alternativas?"
mosquito
1
Lea más allá del segundo párrafo y llegará a la parte "cómo lo hacen otros idiomas". (PD: la otra respuesta tampoco muestra alternativas)
Cefalópodo
44
@gnat ¿No está diciendo indirectamente que la alternativa es usar funciones de primera clase? Si lo que desea es un cuadro negro que pueda crear objetos, sus opciones son una fábrica o una función, y una fábrica es solo una función disfrazada.
Doval
3
"En muchos lenguajes dinámicos" es un poco engañoso, ya que lo que realmente se necesita es un lenguaje con funciones de primera clase. "Dinámico" es ortogonal a esto.
Andres F.
Es cierto, pero es una propiedad que a menudo se encuentra en lenguajes dinámicos. Mencioné la necesidad de una función de primera clase al comienzo de mi respuesta.
Cefalópodo