oop - objetivo - ejercicio práctico declaración de cambio
Las declaraciones de cambio son malas? (8)
Recientemente descubrí que las declaraciones de cambio son malas en OOP, particularmente de "Código limpio" (p37-39) de Robert Martin.
Pero considere esta escena: estoy escribiendo un servidor del juego, recibiendo mensajes de los clientes, que contienen un número entero que indica la acción del jugador, como mover, atacar, elegir un elemento ... etc., habrá más de 30 acciones diferentes. Cuando estoy escribiendo código para manejar estos mensajes, sin importar en qué soluciones pienso, tendrá que usar el conmutador en algún lugar. ¿Qué patrón debo usar si no cambio la declaración?
Desde la perspectiva de los patrones de diseño puede usar el patrón de comando para su escenario dado. (Ver http://en.wikipedia.org/wiki/Command_pattern ).
Si se encuentra utilizando repetidamente instrucciones de cambio en el paradigma de OOP, esto es una indicación de que sus clases pueden no estar bien diseñadas. Supongamos que tiene un diseño adecuado de súper y subclases y una buena cantidad de polimorfismo. La lógica detrás de las declaraciones switch debe ser manejada por las subclases.
Para obtener más información sobre cómo se eliminan estas instrucciones de cambio e introducir las subclases apropiadas, le recomiendo que lea el primer capítulo de Refactoring by Martin Fowler. O puede encontrar diapositivas similares aquí http://www1.informatik.uni-wuerzburg.de/database/courses/pi2_ss03_dir/RefactoringExampleSlides.pdf . (Diapositiva 44)
El patrón de Strategy
viene a la mente.
El patrón de estrategia pretende proporcionar un medio para definir una familia de algoritmos, encapsular cada uno como un objeto y hacerlos intercambiables. El patrón de estrategia permite que los algoritmos varíen independientemente de los clientes que los usan.
En este caso, la "familia de algoritmos" son sus diferentes acciones.
En cuanto a las sentencias de conmutación, en "Código limpio", Robert Martin dice que intenta limitarse a una declaración de cambio por tipo. No eliminarlos por completo.
La razón es que las instrucciones de cambio no se adhieren a OCP .
Las instrucciones ''incorrectas'' son a menudo aquellas que cambian de tipo de objeto (o algo que podría ser un tipo de objeto en otro diseño). En otras palabras, la codificación de algo que podría ser mejor manejado por polimorfismo. Otros tipos de declaraciones de interruptor bien podrían estar bien
Necesitará una declaración de cambio, pero solo una. Cuando reciba el mensaje, llame a un objeto Factory para devolver un objeto de la subclase Message correspondiente (Move, Attack, etc.), luego llame a un método message-> doit () para hacer el trabajo.
Eso significa que si agrega más tipos de mensajes, solo el objeto de fábrica debe cambiar.
Las instrucciones de switch
IMO no son malas , pero se deben evitar si es posible. Una solución sería usar un Map
donde las claves son los comandos, y los valores Command
oponen con un método execute()
. O una List
si sus comandos son numéricos y no tienen espacios vacíos.
Sin embargo, por lo general, utilizaría instrucciones de switch
al implementar patrones de diseño; Un ejemplo sería usar un patrón de Cadena de responsabilidad para manejar los comandos dados cualquier comando "id" o "valor". (El patrón de Strategy también se mencionó). Sin embargo, en su caso, también podría observar el patrón de http://en.wikipedia.org/wiki/Command_pattern .
Básicamente, en OOP, tratará de usar otras soluciones además de confiar en los bloques de switch
, que usan un paradigma de programación de procedimientos. Sin embargo, cuándo y cómo usar cualquiera de ellos es algo su decisión. Personalmente, a menudo uso bloques de switch
cuando uso el patrón de Factory , etc.
Una definición de organización de código es:
- un paquete es un grupo de clases con una API coherente (por ejemplo: API de recopilación en muchos marcos)
- una clase es un conjunto de funcionalidades coherentes (por ejemplo, una clase de
Math
... - un método es una funcionalidad; debería hacer una cosa y una sola cosa. (por ejemplo: agregar un elemento en una lista puede requerir ampliar esa lista, en cuyo caso el método
add
dependerá de otros métodos para hacer eso y no realizará esa operación, porque no es su contrato).
Por lo tanto, si su instrucción switch
realiza diferentes tipos de operaciones, está " violando " esa definición; mientras que usar un patrón de diseño no lo hace, ya que cada operación se define en su propia clase (su propio conjunto de funcionalidades).
No lo compro. Estos fanáticos de OOP parecen tener máquinas que tienen una RAM infinita y un rendimiento increíble. Obviamente, con la memoria RAM integrada no tiene que preocuparse por la fragmentación de RAM y los impactos en el rendimiento que tiene cuando continuamente crea y destruye pequeñas clases de ayuda. Parafraseando una cita del libro "Código hermoso": "Todos los problemas en la informática se pueden resolver con otro nivel de abstracción"
Use un interruptor si lo necesita. Los compiladores son bastante buenos generando código para ellos.
Ponía los mensajes en una matriz y luego combinaba el elemento con la clave de la solución para mostrar el mensaje.
Un interruptor es como cualquier otra estructura de control. Hay lugares donde es la mejor / más limpia solución, y muchos más lugares donde es completamente inapropiado. Simplemente se abusa mucho más que otras estructuras de control.
En el diseño OO, generalmente se considera preferible, en una situación como la suya, utilizar diferentes clases / tipos de mensajes que heredan de una clase de mensaje común, luego utilizar métodos sobrecargados para diferenciar "automáticamente" entre los diferentes tipos.
En un caso como el tuyo, puedes usar una enumeración que se correlacione con tus códigos de acción, luego adjuntar un atributo a cada valor enumerado que te permitirá usar genéricos o construcción de tipos para construir diferentes objetos de subclase de acción para que el método de sobrecarga trabajo.
Pero eso es un verdadero dolor
Evalúe si hay una opción de diseño, como la enumeración que es factible en su solución. Si no, solo usa el interruptor.
Usa comandos Envuelva la acción en un objeto y deje que el polimorfismo haga el cambio por usted. En C ++ ( shared_ptr
es simplemente un puntero, o una referencia en términos de Java. Permite el despacho dinámico):
void GameServer::perform_action(shared_ptr<Action> op) {
op->execute();
}
Los clientes eligen una acción para realizar, y una vez que lo hacen envían esa acción al servidor para que el servidor no tenga que realizar ningún análisis:
void BlueClient::play() {
shared_ptr<Action> a;
if( should_move() ) a = new Move(this, NORTHWEST);
else if( should_attack() ) a = new Attack(this, EAST);
else a = Wait(this);
server.perform_action(a);
}