design patterns - pattern - patrón de comando que devuelve el estado
mediator design pattern (9)
Una vez tuve una discusión sobre el diseño, en relación con el patrón de comando. Mi interlocutor afirmó que un objeto de comando no debería devolver el estado (exitoso, sin éxito y por qué) después de que se llama al método .execute (). La razón es que no debe preocuparse si el comando se ejecuta o no, porque el comando no debe contener ningún estado. Sin embargo, debe verificar después de la invocación si el comando tuvo el efecto esperado. Otro punto que argumentó fue que en la Banda de los Cuatro, el patrón de comando no presenta este caso (de estado de retorno).
Reclamé el punto opuesto. El GoF no presenta este caso, pero un patrón se puede modelar según sus necesidades. Si un comando no tiene éxito, el cliente invocador debe recibir una prueba del estado y, finalmente, implementar una reacción apropiada. Al forzar al cliente a verificar si la acción lograba el éxito era propenso a errores y producía código duplicado. Además, hay casos en que el comando produce un resultado (por ejemplo, un comando que agrega una línea a un gráfico, de alguna manera tendrá el ID de línea para regresar al cliente), y pretender tener comandos sin estado significaba que tenía que "pescar" el nuevo identificador de objeto del modelo de datos.
Al final, llegamos a un compromiso al no devolver el estado, pero manteniendo el ID de los objetos recién creados en el objeto de comando, y la aplicación funcionó bastante bien de todos modos, pero ahora tengo curiosidad por conocer su opinión también.
¿Podría ser el problema aquí que el comando será ejecutado por alguna clase ejecutor que no tendrá conocimiento directo de lo que realmente hace el comando?
Si estamos hablando de agregar un tipo de devolución al método de ejecución, existe la posibilidad de exponer tipos de devolución específicos de implementación al ejecutor. Con esto quiero decir que estás abriendo una puerta a una situación donde los diferentes comandos pueden tener diferentes conjuntos de valores de retorno. Si el ejecutor fuera a tener que manejar estos, entonces se uniría más estrechamente a las implementaciones del comando.
Sin embargo, a menudo he dado estados de comandos, lo que les permite ser configurados con valores de trabajo por el cliente en la construcción, y luego proporcionar getters para permitir que el cliente extraiga los resultados de la ejecución del comando al finalizar. En este caso, puede que no haya seguido estrictamente el patrón de comando, pero el diseño funcionó bien y, a menos que haya un código definido, ¿esto es realmente un problema?
Nota: Dicho esto, estaría interesado en escuchar las opiniones sobre por qué esto puede ser un olor a código.
Como dijo en su pregunta:
Si un comando no tiene éxito, el CLIENTE invocador debe recibir una prueba del estado y, finalmente, desplegar una reacción apropiada.
En ese caso, tiro excepciones de tiempo de ejecución como estado, que contiene la información necesaria al respecto. Podrías probarlo.
Saludos,
En este momento no tengo Patrones de diseño: Elementos de software reutilizable orientado a objetos, pero estoy bastante seguro de que los autores incluso dicen que los patrones de diseño que presentan son un modelo que puede modificarse para ajustarse a un diseño específico. situación.
Esta pregunta corta al núcleo de lo que es un patrón de diseño: una plantilla. No es algo que deba ser implementado por el libro. Identificó un caso en el que una modificación lógica del patrón, tal como se presenta en el libro, habría ayudado a la aplicación, y eso está perfectamente bien, especialmente una vez que evalúa los beneficios y los costos.
En mi software CAD / CAM, los ensamblajes que contienen los comandos hacen referencia a los ensamblados que contienen las interfaces y la jerarquía de objetos que contiene los diversos elementos de la interfaz de usuario de mi software. Es similar a una vista pasiva
Los comandos pueden manipular la interfaz de usuario a través de las interfaces de visualización e informar cualquier error.
Básicamente va
Los formularios implementan IFormInterfaces y se registran con ScreenViews en el EXE
Los ScreenObjects implementan IScreenView y se registran con el ensamblaje ScreenView, así como también toman comandos de los conjuntos de comandos
Los ensambles de comando hacen referencia al ensamblaje de ScreenView y al modelo
ScreenView Assembly poco más que una colección de interfaces de visualización y contiene la implementación de la aplicación.
Esto definitivamente es discutible, pero muestra claramente los dos estilos de pensamiento:
- Compruebe si algo está bien, y luego proceda en consecuencia
- Proceda de todos modos y lidiar con eso si pasa algo malo
No creo que una forma sea mejor que la otra. Por ejemplo, en Java, generalmente es mejor no abusar de su manejo de excepciones y solucionar cualquier posible problema antes de simplemente arrojar sus manos (y excepciones) al aire. Con Python, es más preferible seguir adelante e intentar hacer lo que sea, sin importar el código de estado, y dejar que cualquier excepción simplemente se trate en consecuencia.
Realmente depende de usted si desea o no que el patrón de comando devuelva un estado.
Hay dos preguntas en la pregunta con respuestas múltiples :) La primera pregunta es si un comando devuelve un estado de error.
No hay una respuesta clara para cada programa cada vez que aplica el patrón, tiene que pensarlo de nuevo.
Una de las cosas en las que debes pensar es:
- ¿Estoy agregando más acoplamiento a muchos comandos y a los clientes solo para algunos casos de error específicos?
En el peor de los casos, tiene muchos comandos que no se preocupan por los errores, pero uno o dos comandos hacen algo que es importante para el cliente saber si funcionó. Ahora agrega excepciones comprobadas a la interfaz y, por lo tanto, cada cliente y cada comando están obligados a gestionar errores y están acoplados a la excepción. Si tiene un cliente que solo trata con comandos que no lanzan las excepciones, tiene una gran sobrecarga en su código.
Esto es algo que no quieres tener. Así que puede mover los comandos que necesitan manejo de errores fuera de la estructura de comandos porque parecen ser diferentes de los otros comandos, o si su lenguaje lo permite, puede agregar excepciones de tiempo de ejecución que solo son manejadas por los clientes que se preocupan y lanzan los comandos que necesitan arrojarlos.
El otro extremo es que cada comando puede fallar y el cliente tiene una forma consistente de manejar los errores, lo que significa que los errores no dependen del comando específico. El cliente no tiene que saber qué tipo de comando falló; puede manejar cada error de la misma manera. Ahora podría hacer que la interfaz del comando devuelva un estado de error y el cliente pueda manejar los errores. Pero lidiar con los errores no debe depender del tipo de comando para el cliente.
La segunda pregunta es: ¿debería un comando tener un estado?
Hay arquitecturas donde un comando necesita un estado y otro donde no necesitan un estado.
Algunas posibilidades para decidir esto:
- Si desea deshacer su comando, los comandos deben tener un estado.
Si los comandos solo se usan para ocultar una función que funciona en un conjunto pequeño de parámetros y los resultados solo dependen de la misma del comando como el patrón de estado, no es necesario un estado y puede usar el mismo objeto sobre y más.
Si usa el comando para comunicarse entre hilos y desea transferir datos de un hilo a otro, el comando necesita un estado.
- ... Si hay algo que crees que debería estar en esta lista, deja un comentario.
Me referiré a "Patrones de diseño de Head First". Los ejemplos que usan para el patrón de comando son:
- el escenario del comensal (el cliente crea el pedido, el mesero lo invoca gritando al personal de la cocina y el personal de la cocina recibe el pedido)
- el escenario de control remoto (la persona hace clic en un botón, el control remoto invoca el comando y el dispositivo lo recibe)
Obviamente, en el primer caso, el receptor produce algún tipo de estado: "aquí está la comida", o "no tenemos pan de centeno". En un restaurante elegante, puede hacer esto mediante el manejo de excepciones en un nivel superior (el maitre se acerca a la mesa y se disculpa, ofrece un sustituto y compila su postre), y los camareros no tienen que hacer nada más que invocar adecuadamente los comandos. Pero en un restaurante, quizás el cocinero siga adelante y sustituya el pan integral: el personal (y el cliente) deben ser capaces de manejarlo sin mirar el mostrador preguntándose "¿dónde está mi atún con centeno?" Esto no se trata directamente en el libro, pero creo que es claramente un caso válido.
Pero en el segundo escenario, el invocador deliberadamente se vuelve estúpido. No te mostrará un error si algo va mal, simplemente no tendrá ningún efecto. Toda la inteligencia está en el cliente para determinar si su comando fue exitoso oportunamente ("mierda, olvidé conectarlo"), o en el receptor para descubrir qué hacer ("reproducir CD: cerrar bandeja de CD" primero").
No soy un experto, pero diría que devolver el estado al invocador es totalmente correcto para algunas aplicaciones.
Muy buena discusión. He estado en esta cuestión filosófica durante horas, y llegué a una solución que satisface mi obsesión. (La razón por la que amo estas cosas es que combina lógica concreta y abstracta: diseños booleanos +).
Considere brevemente el uso de Excepciones para devolver resultados. Abandoné esa idea porque en muchos casos eliminaría el desacoplamiento, el corazón del patrón en sí, como algunos de ustedes han notado. Además, el resultado frecuentemente no es una excepción, sino un valor de retorno estándar. Probablemente tenga úlceras.
Finalmente, escribí un cliente que instancia un receptor consigo mismo, manteniendo toda la lógica en el receptor donde pertenece. El cliente simplemente llama al comando execute () y continúa. El receptor puede llamar a los métodos públicos en el cliente. No hay nada que devolver.
Aquí hay un código de muestra. No escribí la clase de comando porque creo que obtendrás la idea sin eso. Su método execute () llama al método run () del receptor.
El cliente:
Class ClientType{
CommandType m_Command;
ReceiverType m_Receiver;
boolean m_bResult;
ClientType(){
m_Receiver = new ReceiverType(this);
m_Command = new CommandType(m_Receiver);
}
public void run(){
...
m_Command.execute();
}
/* Decoupled from both the
* command and the receiver.
* It''s just a public function that
* can be called from anywhere. /
public setResult(boolean bResult){
m_bResult = bResult;
}
}
El receptor:
Class ReceiverType{
ClientType m_Client;
boolean m_bResult;
ReceiverType(ClientType client){
m_Client = client;
}
public void run(){
...
m_Client.setResult(m_bResult);
}
}
A primera vista, podría parecer que violé el requisito de desacoplamiento. Pero considere que el cliente no sabe nada sobre la implementación del receptor. El hecho de que el receptor sepa que debe llamar a los métodos públicos en el cliente es una tarifa estándar. Los receptores siempre saben qué hacer con sus objetos de parámetros. No hay dependencias El hecho de que el constructor del receptor tome un parámetro ClientType es irrelevante. Podría ser cualquier objeto.
Sé que este es un hilo viejo, pero espero que algunos de ustedes toquen de nuevo. Siéntase libre de romper mi corazón si ve un defecto. Eso es un poco lo que hacemos.
Otro compromiso es exponer un "controlador de excepción" de propiedad en un comando concreto que puede fallar. De esta forma, el creador del comando puede manejar la excepción y no agrega sobrecarga de código a su cliente. Esto es muy útil cuando la mayoría de sus comandos no deberían fallar.