strategy patterns pattern gof dofactory book c# oop design-patterns

patterns - state pattern c#



Grandes instrucciones de cambio: ¿mal OOP? (14)

El comando puede ser uno de los 100 comandos diferentes

Si necesita hacer una de cada 100 cosas diferentes, no puede evitar tener una sucursal de 100 vías. Puede codificarlo en flujo de control (switch, if-elseif ^ 100) o en datos (un mapa de 100 elementos desde string hasta command / factory / strategy). Pero estará allí.

Puede tratar de aislar el resultado de la rama de 100 vías de las cosas que no necesitan conocer ese resultado. Quizás solo 100 métodos diferentes están bien; no es necesario inventar objetos que no necesita si eso hace que el código sea difícil de manejar.

Siempre he sido de la opinión de que las declaraciones de cambio grandes son un síntoma del mal diseño de OOP. En el pasado, he leído artículos que analizan este tema y me han proporcionado enfoques basados ​​en POO altà © nticos, generalmente basados ​​en el polimorfismo para ejemplificar el objeto adecuado para manejar el caso.

Ahora estoy en una situación que tiene un enunciado de cambio monstruoso basado en una secuencia de datos de un socket TCP en el que el protocolo consiste básicamente en un comando terminado en una nueva línea, seguido de líneas de datos, seguido de un marcador final. El comando puede ser uno de los 100 comandos diferentes, por lo que me gustaría encontrar una forma de reducir esta declaración de cambio de monstruo a algo más manejable.

He buscado en Google para encontrar las soluciones que recuerdo, pero lamentablemente, Google se ha convertido en un páramo de resultados irrelevantes para muchos tipos de consultas en estos días.

¿Hay algún patrón para este tipo de problema? ¿Alguna sugerencia sobre posibles implementaciones?

Lo único que pensé fue utilizar una búsqueda en el diccionario, haciendo coincidir el texto del comando con el tipo de objeto para crear instancias. Esto tiene la agradable ventaja de simplemente crear un nuevo objeto e insertar un nuevo comando / tipo en la tabla para cualquier comando nuevo.

Sin embargo, esto también tiene el problema de tipo explosión. Ahora necesito 100 nuevas clases, además tengo que encontrar una forma de relacionarlas limpiamente con el modelo de datos. ¿Es la "verdadera declaración de cambio" realmente el camino a seguir?

Apreciaría sus pensamientos, opiniones o comentarios.


Creo que este es uno de los pocos casos en que los interruptores grandes son la mejor respuesta a menos que se presente alguna otra solución.


Hace poco tuve un problema similar con una gran declaración de cambio y me libré del feo interruptor con la solución más simple , una tabla de búsqueda y una función o método que devuelve el valor esperado. el patrón de comando es una buena solución, pero tener 100 clases no es agradable, creo. entonces tuve algo como:

switch(id) case 1: DoSomething(url_1) break; case 2: DoSomething(url_2) break; .. .. case 100 DoSomething(url_100) break;

y he cambiado por:

string url = GetUrl(id); DoSomthing(url);

GetUrl puede ir a DB y devolver la URL que está buscando, o podría ser un diccionario en memoria con las 100 URL. Espero que esto pueda ayudar a cualquiera por ahí cuando reemplace una enorme declaración de interruptor monstruoso.


Hay dos cosas que vienen a la mente cuando se habla de una declaración de conmutación grande:

  1. Viola OCP: podría mantener una función grande continuamente.
  2. Podría tener un mal rendimiento: O (n).

Por otro lado, una implementación de mapa puede ajustarse a OCP y podría funcionar con potencialmente O (1).


La mejor manera de manejar este problema en particular: la serialización y los protocolos limpios es usar un IDL y generar el código de cálculo de referencias con instrucciones de cambio. Debido a que cualquiera que sea el patrón (prototipo de fábrica, patrón de comando, etc.) que intente utilizar de otra manera, tendrá que inicializar una asignación entre una ID de comando / cadena y un puntero de clase / función, de alguna manera y se ejecutará más lento que las instrucciones de conmutación, ya que el compilador puede usar una búsqueda hash perfecta para las declaraciones de cambio.


Piense en cómo se escribió originalmente Windows en la bomba de mensajes de la aplicación. Apestaba Las aplicaciones se ejecutarían más lentamente con más opciones de menú que haya agregado. A medida que el comando buscado terminó cada vez más hacia la parte inferior de la instrucción switch, hubo una espera cada vez más larga para la respuesta. No es aceptable tener declaraciones de conmutación largas, punto. Creé un daemon de AIX como controlador de comandos POS que podía manejar 256 comandos únicos sin siquiera saber qué había en la secuencia de solicitud recibida a través de TCP / IP. El primer carácter de la secuencia era un índice en una matriz de funciones. Cualquier índice no utilizado se estableció en un manejador de mensajes predeterminado; regístrate y di adiós.


Puede obtener algún beneficio de un patrón de comando .

Para OOP, es posible que pueda colapsar varios comandos similares en una sola clase, si las variaciones de comportamiento son lo suficientemente pequeñas, para evitar una explosión de clase completa (sí, ya puedo escuchar a los gurús de OOP chillar sobre eso). Sin embargo, si el sistema ya es OOP, y cada uno de los más de 100 comandos es realmente único, simplemente conviértalos en clases únicas y aproveche la herencia para consolidar las cosas comunes.

Si el sistema no es OOP, entonces no agregaría OOP solo para esto ... puede usar fácilmente el patrón de comando con una búsqueda simple de diccionario y punteros a funciones, o incluso llamadas de función generadas dinámicamente en función del nombre del comando, dependiendo de el idioma. Luego puede agrupar lógicamente las funciones asociadas en bibliotecas que representan una colección de comandos similares para lograr una separación manejable. No sé si hay un buen término para este tipo de implementación ... Siempre lo considero un estilo de "despachador", basado en el enfoque de MVC para manejar las URL.


Puede usar un diccionario (o un mapa hash si está codificando en Java) (se llama desarrollo impulsado por tablas de Steve McConnell).


Sí, creo que las declaraciones de casos grandes son un síntoma de que el código de uno puede mejorarse ... generalmente implementando un enfoque más orientado a objetos. Por ejemplo, si me encuentro evaluando el tipo de clases en una instrucción switch, eso casi siempre significa que probablemente podría usar Generics para eliminar la instrucción switch.


También puede tomar un enfoque de idioma aquí y definir los comandos con datos asociados en una gramática. Luego puede usar una herramienta generadora para analizar el idioma. He usado Irony para ese propósito. Alternativamente puede usar el patrón de Interpreter.

En mi opinión, el objetivo no es construir el modelo OO más puro, sino crear un sistema flexible, extensible, sostenible y potente.


Una forma en la que veo que podría mejorar sería hacer que su código sea manejado por los datos, de modo que, por ejemplo, para cada código coincida con algo que lo maneje (función, objeto). También puede usar la reflexión para asignar cadenas que representan los objetos / funciones y resolverlos en tiempo de ejecución, pero es posible que desee realizar algunos experimentos para evaluar el rendimiento.


Veo el patrón de estrategia. Si tengo 100 estrategias diferentes ... que así sea. La declaración de cambio gigante es fea. ¿Son todos los comandos nombres de clase válidos? Si es así, simplemente use los nombres de comando como nombres de clase y cree el objeto de estrategia con Activator.CreateInstance.


Veo tener dos instrucciones de conmutación como un síntoma de diseño no OO, donde el tipo de conexión enum podría reemplazarse por varios tipos que proporcionan implementaciones diferentes de una interfaz abstracta; por ejemplo, el siguiente ...

switch (eFoo) { case Foo.This: eatThis(); break; case Foo.That: eatThat(); break; } switch (eFoo) { case Foo.This: drinkThis(); break; case Foo.That: drinkThat(); break; }

... debería ser reescrito como ...

IAbstract { void eat(); void drink(); } class This : IAbstract { void eat() { ... } void drink() { ... } } class That : IAbstract { void eat() { ... } void drink() { ... } }

Sin embargo, una instrucción switch no es un indicador tan fuerte de que la instrucción switch deba reemplazarse por otra cosa.


Yo diría que el problema no es la gran declaración de cambio, sino la proliferación de código que contiene y el abuso de variables de ámbito erróneo.

Experimenté esto en un proyecto yo mismo, cuando más y más código entró en el interruptor hasta que se volvió imposible de mantener. Mi solución fue definir la clase de parámetro que contenía el contexto de los comandos (nombre, parámetros, lo que sea, recopilados antes del cambio), crear un método para cada declaración de caso y llamar a ese método con el objeto de parámetro del caso.

Por supuesto, un despachador de comandos completamente OOP (basado en la magia como la reflexión o mecanismos como la activación de Java) es más hermoso, pero a veces solo quieres arreglar las cosas y hacer el trabajo;)