2009-06-28 14 views
13

Recientemente, estaba discutiendo con otro programador la mejor manera de refactorizar un método enorme (1000 líneas) lleno de declaraciones "if".¿Cuáles son las ventajas de la cadena de responsabilidad frente a las listas de clases?

El código está escrito en Java, pero creo que este problema podría ocurrir en otros lenguajes, como C# también.

Para resolver este problema, sugirió usar un patrón de cadena de responsabilidad. Propuso tener una clase básica de "Manejador". Entonces, "Handler1", "Handler2", etc. extenderían "Handler".
Entonces, los manejadores tendrían un método "getSuccessor", que devolvería null (si era el último de la cadena) o el siguiente controlador de la cadena.
Luego, una función "handleRequest (Request)" trataría con Request o la pasaría al siguiente de la cadena y, si ninguna de las soluciones previas funcionó, devolvería simplemente null o arrojaría una excepción.
Para agregar un nuevo controlador a la cadena, el codificador iría al último elemento de la cadena y le indicaría que había un elemento nuevo. Para hacer algo, simplemente llama a handleRequest en el primer elemento de la cadena.

Para resolver este problema, sugerí usar un enfoque diferente.
Tendría una clase base "Handler" también, con "Handler1", "Handler2", al igual que el método anterior mencionado.
Sin embargo, no habría ningún método "getSuccessor". En cambio, tendría una clase Collection con una lista de controladores (un Vector, una ArrayList, o lo que sea mejor en este caso).
La función handleRequest todavía existiría, pero no propagaría la llamada a los siguientes controladores. Solo procesaría la solicitud o devolvería null.
para manejar una petición, se podría usar

for(Handler handle : handlers){ 
    result = handle.handleRequest(request); 
    if(result!=null) return result; 
} 
throw new CouldNotParseRequestException(); //just like in the other approach 

O, para evitar la duplicación de código, un "parseRequest (petición)" método podría ser añadido a la clase de colección. Para agregar un nuevo controlador, uno iría al constructor de la colección (o al bloque static {}, o algo similar) y simplemente agregaría el código "addHandler (new Handler3());".

Exactamente qué ventajas de la cadena de responsabilidad me estoy perdiendo con este enfoque? ¿Qué método es mejor (suponiendo que es el mejor método)? ¿Por qué? ¿Qué posibles errores y problemas puede causar cada método de diseño?

Para aquellos que necesitan contexto, aquí es lo que el código original parecía:

if(x instanceof Type1) 
{ 
//doSomething1 
} else if(x instanceof Type2) 
{ 
//doSomething2 
} 
//etc. 

Respuesta

6

Me gusta más tu idea con la colección que esos sucesores.Hace que sea fácil y claro manipular este conjunto de manejadores: la interfaz de colecciones es bien conocida y todos entienden cómo iterar sobre una lista o qué no.

Si usa esta forma de sucesor sugerida por un amigo, tenga cuidado de no caer en una recursión muy profunda (a menos que su plataforma admita llamadas finales, no sé si las JVM son capaces de eso).

No recomendaría agregar ningún método a la colección. Obtiene un diseño mucho más complicado que es más difícil de comprender y más difícil de modificar. Hay dos preocupaciones separadas: almacenar un conjunto de controladores y la interpretación de estos controladores como una cadena de responsabilidad. Un método que maneja las solicitudes iterando sobre una colección tiene un nivel de abstracción más alto que los métodos de limpieza de la colección, por lo tanto, no debería pertenecer a la interfaz de colección.

+0

recursividad muy profunda no debería ser un problema. Es relativamente pequeño (en ninguna parte cerca de las miles de llamadas a métodos necesarias para apilar el sistema de desbordamiento). – luiscubal

0

No se puede decir si Cadena de Responsabilidad es su respuesta, o incluso si se aplica GoF. El visitante puede ser lo correcto. No hay suficiente información para estar seguro.

Podría ser que su problema se pueda manejar con un buen polimorfismo pasado de moda. O tal vez un mapa que usó claves para seleccionar el objeto controlador apropiado.

Mantenga 'hacer lo más simple posible' en mente. No salte al complejo hasta que haya demostrado que lo necesita.

Recientemente he leído sobre el Anti-IF campaign que promueve esta idea. Suena bastante pertinente aquí.

4

Cuál es el mejor enfoque depende de lo que sus controladores quieren hacer.

Si los controladores se pueden manejar por completo una solicitud de petición por su cuenta, su enfoque está muy bien. Los manejadores no tienen una referencia a otros manejadores, lo que hace que la interfaz del manejador sea simple. A diferencia de la implementación estándar de Chain of Responsibility, puede agregar o eliminar manejadores del centro de la cadena. De hecho, puedes elegir construir diferentes cadenas dependiendo del tipo de solicitud.

Uno de los problemas con su enfoque es que un controlador no puede hacer pre-tratamiento o post-procesamiento de la solicitud. Si se requiere esta funcionalidad, la cadena de responsabilidad es mejor. En CoR, el controlador es el responsable de delegar en el siguiente controlador de la cadena, por lo que el controlador puede hacer un procesamiento previo y/o posterior procesamiento, incluida la modificación o sustitución de la respuesta del siguiente controlador de la cadena. De esta forma, CoR es muy similar a Decorator; es solo la intención que es diferente.

Dado que en CDR, el controlador mantiene una referencia al siguiente punto de la cadena, no se puede añadir o eliminar elementos del centro de la cadena. Una variación del CDR que le permite agregar o eliminar elementos del centro de la cadena es una cadena de filtro (consulte, por ejemplo, javax.servlet.FilterChain).

El ejemplo de código que mostró fue un montón de sentencias "if" que hicieron un comportamiento diferente en función del tipo de un objeto. Si eso es típico del código que está limpiando, simplemente puede tener un mapa desde el tipo de solicitud hasta el controlador requerido.

Otro enfoque para la eliminación de "if" es la herencia. Si tenía algún comportamiento que tenía que hacer, y había una variación para un servidor web y otra variación para un servidor SOAP, podría tener un WebServerRequestHandler y un SoapServerRequestHandler, cada uno extendiendo RequestHandler. La ventaja con la herencia es que hay un lugar más claro para poner la lógica que es común para ambos tipos de solicitud. La desventaja es que, dado que Java no tiene herencia múltiple, solo puede modelar problemas unidimensionales.

Cuestiones relacionadas