Patrones de diseño para evitar [cerrado]

105

Mucha gente parece estar de acuerdo en que el patrón Singleton tiene una serie de inconvenientes y algunos incluso sugieren evitar el patrón por completo. Aquí hay una excelente discusión . Dirija cualquier comentario sobre el patrón Singleton a esa pregunta.

Mi pregunta : ¿Existen otros patrones de diseño que deberían evitarse o usarse con mucho cuidado?

Brian Rasmussen
fuente
Solo tengo que notar una gran lista de Antipatterns de diseño deviq.com/antipatterns
Desarrollador
@casperOne ¿por qué cerraste la pregunta? La pregunta era legítima.
bleepzter

Respuestas:

149

Los patrones son complejos

Todos los patrones de diseño deben usarse con cuidado. En mi opinión , debería refactorizar hacia patrones cuando hay una razón válida para hacerlo en lugar de implementar un patrón de inmediato. El problema general con el uso de patrones es que agregan complejidad. El uso excesivo de patrones hace que una aplicación o un sistema determinados sean difíciles de desarrollar y mantener.

La mayoría de las veces, existe una solución simple y no es necesario aplicar ningún patrón específico. Una buena regla general es usar un patrón siempre que los fragmentos de código tiendan a ser reemplazados o necesiten cambiarse con frecuencia y estar preparado para asumir la advertencia del código complejo al usar un patrón.

Recuerde que su objetivo debe ser la simplicidad y emplear un patrón si ve una necesidad práctica de admitir cambios en su código.

Principios sobre patrones

Puede parecer discutible usar patrones si evidentemente pueden conducir a soluciones complejas y sobre-diseñadas. Sin embargo, es mucho más interesante para un programador leer sobre técnicas y principios de diseño que sientan las bases para la mayoría de los patrones. De hecho, uno de mis libros favoritos sobre 'patrones de diseño' enfatiza esto al reiterar qué principios son aplicables al patrón en cuestión. Son lo suficientemente simples como para ser útiles que los patrones en términos de relevancia. Algunos de los principios son lo suficientemente generales como para abarcar más que la programación orientada a objetos (OOP), como el principio de sustitución de Liskov , siempre que pueda construir módulos de su código.

Hay una multitud de principios de diseño, pero los descritos en el primer capítulo del libro GoF son bastante útiles para empezar.

  • Programe en una "interfaz", no en una "implementación". (Banda de cuatro 1995: 18)
  • Favorece la 'composición de objetos' sobre la 'herencia de clases'. (Banda de cuatro 1995: 20)

Deje que esos se hundan en usted por un tiempo. Cabe señalar que cuando se escribió GoF, una interfaz significa cualquier cosa que sea una abstracción (que también significa superclases), que no debe confundirse con la interfaz como un tipo en Java o C #. El segundo principio proviene del uso excesivo observado de la herencia que, lamentablemente, todavía es común en la actualidad .

A partir de ahí, puede leer sobre los principios SOLID que dio a conocer Robert Cecil Martin (también conocido como el tío Bob) . Scott Hanselman entrevistó al tío Bob en un podcast sobre estos principios :

  • S Responsabilidad ingle Principio
  • O pluma cerrado Principio
  • Principio de sustitución de L iskov
  • Me nterface Segregación Principio
  • D ependency Inversión Principio

Estos principios son un buen comienzo para leer y discutir con sus compañeros. Puede encontrar que los principios se entrelazan entre sí y con otros procesos, como la separación de preocupaciones y la inyección de dependencia . Después de hacer TDD por un tiempo, también puede encontrar que estos principios surgen naturalmente en la práctica, ya que necesita seguirlos hasta cierto punto para crear pruebas unitarias aisladas y repetibles .

Spoike
fuente
7
+1 Muy buena respuesta. Parece que todos los programadores (novatos) de hoy conocen sus patrones de diseño o al menos saben que existen. Pero muchos nunca han oído hablar, y mucho menos aplicar, algunos de los principios absolutamente esenciales como la responsabilidad única para gestionar la complejidad de su código.
eljenso
21

El que más preocupaba a los autores de Design Patterns era el patrón "Visitante".

Es un "mal necesario", pero a menudo se usa en exceso y la necesidad a menudo revela un defecto más fundamental en su diseño.

Un nombre alternativo para el patrón "Visitante" es "Despacho múltiple", porque el patrón Visitante es con lo que termina cuando desea utilizar un lenguaje OO de despacho de un solo tipo para seleccionar el código que se utilizará según el tipo de dos (o más) objetos diferentes.

El ejemplo clásico es que tienes la intersección entre dos formas, pero hay un caso aún más simple que a menudo se pasa por alto: comparar la igualdad de dos objetos heterogéneos.

De todos modos, a menudo terminas con algo como esto:

interface IShape
{
    double intersectWith(Triangle t);
    double intersectWith(Rectangle r);
    double intersectWith(Circle c);
}

El problema con esto es que ha acoplado todas sus implementaciones de "IShape". Ha insinuado que siempre que desee agregar una nueva forma a la jerarquía, también deberá cambiar todas las demás implementaciones de "Forma".

A veces, este es el diseño mínimo correcto, pero piénselo bien. ¿Su diseño realmente exige que se envíe en dos tipos? ¿Estás dispuesto a escribir cada una de las explosiones combinatorias de múltiples métodos?

A menudo, al introducir otro concepto, puede reducir la cantidad de combinaciones que realmente tendrá que escribir:

interface IShape
{
    Area getArea();
}

class Area
{
    public double intersectWith(Area otherArea);
    ...
}

Por supuesto, depende, a veces realmente necesitas escribir código para manejar todos esos casos diferentes, pero vale la pena hacer una pausa y pensar antes de dar el paso y usar Visitor. Podría ahorrarte mucho dolor más adelante.

Paul Hollingsworth
fuente
2
Hablando de visitantes, el tío Bob lo usa "todo el tiempo" butunclebob.com/ArticleS.UncleBob.IuseVisitor
Spoike
3
@Paul Hollingsworth ¿Puede proporcionar una referencia de dónde dice que los autores de Design Patterns están preocupados (y por qué están preocupados)?
m3th0dman
16

Singletons: una clase que usa singleton X tiene una dependencia que es difícil de ver y difícil de aislar para las pruebas.

Se utilizan con mucha frecuencia porque son convenientes y fáciles de entender, pero realmente pueden complicar las pruebas.

Ver Los solteros son mentirosos patológicos .

orip
fuente
1
También pueden realizar pruebas de forma sencilla, ya que pueden proporcionarle un único punto para inyectar un objeto Mock. Todo se reduce a conseguir el equilibrio adecuado.
Martin Brown
1
@Martin: Si, por supuesto, es posible cambiar un singelton para la prueba (si no usa la implementación estándar de singelton), pero ¿cómo es eso más fácil que pasar la implementación de prueba en el constructor?
orip
14

Creo que el patrón del método de plantilla generalmente es un patrón muy peligroso.

  • Muchas veces consume su jerarquía de herencia por "razones equivocadas".
  • Las clases base tienden a llenarse de todo tipo de código no relacionado.
  • Te obliga a bloquear el diseño, a menudo bastante temprano en el proceso de desarrollo. (Bloqueo prematuro en muchos casos)
  • Cambiar esto en una etapa posterior se vuelve cada vez más difícil.
krosenvold
fuente
2
Yo agregaría que siempre que use el Método de plantilla, probablemente sea mejor usar Estrategia. El problema con TemplateMethod es que hay reentrada entre la clase base y la clase derivada, que a menudo está demasiado acoplada.
Paul Hollingsworth
5
@Paul: El método de plantilla es excelente cuando se usa correctamente, es decir, cuando las partes que varían necesitan saber mucho sobre las partes que no. Mi opinión es que la estrategia debe usarse cuando el código base solo llama al código personalizado y el método de plantilla debe usarse cuando el código personalizado necesita inherentemente conocer el código base.
dsimcha
sí dsimcha, estoy de acuerdo ... siempre que el diseñador de la clase esté al tanto de esto.
Paul Hollingsworth
9

No creo que debas evitar Design Patterns (DP), y no creo que debas forzarte a usar DP al planificar tu arquitectura. Solo deberíamos usar DP cuando surjan naturalmente de nuestra planificación.

Si definimos desde el principio que queremos utilizar un DP determinado, muchas de nuestras futuras decisiones de diseño estarán influenciadas por esa elección, sin garantía de que el DP que elegimos se adapte a nuestras necesidades.

Una cosa que tampoco deberíamos hacer es tratar a un DP como una entidad inmutable, deberíamos adaptar el patrón a nuestras necesidades.

Entonces, resumiendo, no creo que debamos evitar los DP, deberíamos adoptarlos cuando ya están tomando forma en nuestra arquitectura.

Megacan
fuente
7

Creo que Active Record es un patrón sobreutilizado que fomenta la mezcla de la lógica empresarial con el código de persistencia. No hace un buen trabajo al ocultar la implementación de almacenamiento de la capa del modelo y vincula los modelos a una base de datos. Hay muchas alternativas (descritas en PoEAA) como Table Data Gateway, Row Data Gateway y Data Mapper que a menudo brindan una mejor solución y ciertamente ayudan a proporcionar una mejor abstracción del almacenamiento. Además, su modelo no debería necesitar almacenarse en una base de datos; ¿Qué hay de almacenarlos como XML o acceder a ellos mediante servicios web? ¿Qué tan fácil sería cambiar el mecanismo de almacenamiento de sus modelos?

Dicho esto, Active Record no siempre es malo y es perfecto para aplicaciones más simples donde las otras opciones serían excesivas.

Tim Wardle
fuente
1
Algo cierto, pero depende hasta cierto punto de la implementación.
Mike Woodhouse
6

Es simple ... evite Patrones de Diseño que no le sean claros o con los que no se sienta cómodo .

Por nombrar algunos ...

hay algunos patrones poco prácticos , como por ejemplo:

  • Interpreter
  • Flyweight

también hay algunos más difíciles de entender , como por ejemplo:

  • Abstract Factory - El patrón de fábrica abstracto completo con familias de objetos creados no es tan fácil como parece ser
  • Bridge - Puede volverse demasiado abstracto, si la abstracción y la implementación se dividen en subárboles, pero es un patrón muy útil en algunos casos
  • Visitor - La comprensión del mecanismo de doble despacho es realmente imprescindible

y hay algunos patrones que parecen terriblemente simples , pero que no son una elección tan clara debido a varias razones relacionadas con su principio o implementación:

  • Singleton - patrón no del todo malo, simplemente usado demasiado (a menudo allí, donde no es adecuado)
  • Observer - gran patrón ... solo hace que el código sea mucho más difícil de leer y depurar
  • Prototype - El compilador de operaciones comprueba el dinamismo (que puede ser bueno o malo ... depende)
  • Chain of responsibility - con demasiada frecuencia solo se empuja de manera forzada / artificial en el diseño

Para aquellos "poco prácticos", uno realmente debería pensar en ellos antes de usarlos, porque generalmente hay una solución más elegante en alguna parte.

Para los "más difíciles de entender" ... realmente son de gran ayuda, cuando se usan en lugares adecuados y cuando se implementan bien ... pero son una pesadilla, cuando se usan incorrectamente.

Ahora, que sigue ...

Marcel Toth
fuente
El patrón de peso mosca es imprescindible en cualquier momento cuando utiliza un recurso, a menudo una imagen, más de una vez. No es un patrón, es una solución.
Cengiz Kandemir
5

Espero que no me golpeen demasiado por esto. Christer Ericsson escribió dos artículos ( uno , dos ) sobre el tema de los patrones de diseño en su blog de detección de colisiones en tiempo real . Su tono es bastante duro, y quizás un poco provocativo, pero el hombre sabe lo que hace, así que no lo descartaría como desvaríos de un lunático.

falstro
fuente
Lecturas interesantes. ¡Gracias por los enlaces!
Bombe
3
Los imbéciles producen código incorrecto. ¿Los idiotas con patrones producen peor código que los idiotas que nunca han visto patrones? No creo que lo hagan. Para las personas inteligentes, los patrones proporcionan un vocabulario conocido que facilita el intercambio de ideas. La solución: aprenda patrones y trate solo con programadores inteligentes.
Martin Brown
No creo que sea realmente posible para un verdadero imbécil producir un código peor, sin importar qué herramienta estén usando
1800 INFORMACIÓN
1
Creo que su ejemplo con su prueba universitaria solo demuestra que las personas que desprecian el dominio de su problema y no están dispuestas a estudiarlo durante más de unas pocas horas durante un solo fin de semana producirán respuestas incorrectas cuando intenten resolver problemas.
scriptocalypse
5

Algunos dicen que el localizador de servicios es un anti patrón.

Arnis Lapsa
fuente
También es importante tener en cuenta que a veces es necesario un localizador de servicios. Por ejemplo, cuando no tiene el control adecuado de la instanciación del objeto (por ejemplo, atributos con parámetros no constantes en C #). Pero también es posible utilizar el localizador de servicios CON inyección de ctor.
Sinaesthetic
2

Creo que el patrón del observador tiene mucho por lo que responder, funciona en casos muy generales, pero a medida que los sistemas se vuelven más complejos, se convierte en una pesadilla, necesita notificaciones OnBefore (), OnAfter () y, a menudo, la publicación de tareas asincrónicas para evitar que se repita. fascinación. Una solución mucho mejor es desarrollar un sistema de análisis de dependencia automático que instrumente todos los accesos a objetos (con barreras de lectura) durante los cálculos y crea automáticamente un borde en un gráfico de dependencia.

Jesse Pepper
fuente
4
Entendí todo en su respuesta hasta la palabra "A"
1800 INFORMACIÓN
Es posible que deba expandir o vincular a este análisis de dependencia automático del que habla. También en .NET se utilizan delegados / eventos en lugar del patrón de observador.
Spoike
3
@Spoike: los delegados / eventos son una implementación del patrón de observador
orip
1
Mi resentimiento personal contra Observer es que puede crear pérdidas de memoria en los lenguajes de recolección de basura. Cuando haya terminado con un objeto, debe recordar que el objeto no se limpiará.
Martin Brown
@orip: sí, por eso usa delegados / eventos. ;)
Spoike
2

Un complemento de la publicación de Spoike, Refactoring to Patterns es una buena lectura.

Adeel Ansari
fuente
De hecho, he vinculado al catálogo del libro en Internet. :)
Spoike
Oh! No me molesté en colgarlo. En realidad, justo después de terminar la pregunta, este libro me vino a la mente y luego vi tu respuesta. No pude evitar publicarlo. :)
Adeel Ansari
0

El iterador es un patrón de GoF más que se debe evitar, o al menos usarlo solo cuando no hay ninguna alternativa disponible.

Las alternativas son:

  1. para cada bucle. Esta construcción está presente en la mayoría de los lenguajes convencionales y puede usarse para evitar iteradores en la mayoría de los casos.

  2. selectores a la LINQ o jQuery. Deben usarse cuando para cada uno no sea apropiado porque no todos los objetos del contenedor deben procesarse. A diferencia de los iteradores, los selectores permiten manifestar en un lugar qué tipo de objetos se van a procesar.

Volodymyr Frolov
fuente
Estoy de acuerdo con los selectores. Foreach es un iterador, la mayoría de los lenguajes OO proporcionan una interfaz iterable que se implementa para permitir un foreach.
Neil Aitken
En algunos lenguajes para cada construcción se puede implementar a través de iteradores, pero el concepto de la misma es en realidad de más alto nivel y más cercano a los selectores. Cuando se usa para, cada desarrollador declara explícitamente que todos los elementos del contenedor deben procesarse.
Volodymyr Frolov
Los iteradores son un gran patrón. El anti-patrón implementaría IEnumerable / IEnumerator sin un iterador. Creo que LINQ fue posible gracias al yielditerador. Eric White tiene una gran discusión sobre esto en C # 3.0: blogs.msdn.com/b/ericwhite/archive/2006/10/04/… . Además, consulte la discusión de Jeremy Likness sobre corrutinas con iteradores: wintellect.com/CS/blogs/jlikness/archive/2010/03/23/… .
@Ryan Riley, los iteradores son objetos de bajo nivel y, por lo tanto, deben evitarse en el diseño y el código de alto nivel. Los detalles de la implementación de iteradores y diferentes tipos de selectores no importan aquí. Los selectores, a diferencia de los iteradores, permiten al programador expresar explícitamente lo que quieren procesar y para que sean de alto nivel.
Volodymyr Frolov
Fwiw, la sintaxis alternativa de F # similar a LINQ es `List.map (fun x -> x.Value) xs`, que es casi tan larga como la comprensión de la lista.