2010-12-11 23 views
23

Recientemente me enteré de que las instrucciones de cambio son malas en OOP, en particular de "Código limpio" (p37-39) de Robert Martin.Las declaraciones de interruptor son malas?

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, recoger elemento ... etc., habrá más de 30 diferentes acciones 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?

+0

Ver [Instrucciones de Switch grande: ¿mal OOP? ] (http://stackoverflow.com/questions/505454/large-switch-statements-bad-oop) –

+0

Siempre encuentra esto interesante ya que aparentemente el cambio de código está agregando otro caso en la declaración de cambio, pero no es necesario agregar otra clase. ..algunas veces el purista de OO puede ser un poco "religioso" ... –

+3

'"Considerado dañino" considerado dañino ". – delnan

Respuesta

21

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 mensaje 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 suyo, puede usar una enumeración que se correlaciona con sus códigos de acción, luego adjuntar un atributo a cada valor enumerado que le permitirá usar genéricos o construcción de tipos para construir diferentes objetos de subclase de acción de modo que el método de sobrecarga funcionará.

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.

+1

Supongo que se refería al polimorfismo dinámico en lugar del polimorfismo estático (* métodos sobrecargados *) – Geek

+0

@Toby ¿Puede elaborar esta declaración con el ejemplo: "luego adjunte un atributo a cada valor enumerado que le permitirá usar genéricos o creación de tipos para compilar diferentes objetos de clase de acción para que el método de sobrecarga funcione. "? – beinghuman

13

Me viene a la mente el patrón Strategy.

El patrón de estrategia tiene por objeto 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 switch - en "código limpio", Robert Martin dice que él trata de limitarse a un solo sentencia switch según el tipo. No eliminarlos por completo.

La razón es que las instrucciones de cambio no se adhieren a OCP.

3

Colocaría los mensajes en una matriz y luego haría coincidir el elemento con la clave de la solución para mostrar el mensaje.

+0

no todo tiene que ser OOP. Realmente me gusta esta respuesta. – grasshopper

14

Las instrucciones de conmutación 'Incorrectas' suelen ser aquellas que cambian el 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 instrucciones de cambio podrían estar bien

Necesitará una instrucció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.

+0

¿Qué tal si hacemos cosas como 'Map , Thing>' esto es muy similar a "hacer un cambio en las clases", ¿se considera esto como una práctica correcta? – YoTengoUnLCD

4

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 repetidamente usando instrucciones de conmutación en el paradigma 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)

2

IMO switch declaraciones no son malo, 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 se oponen con un método execute(). O List si sus comandos son numéricos y no tienen espacios vacíos.

Sin embargo, por lo general, utilizaría las declaraciones switch al implementar patrones de diseño; un ejemplo sería usar un patrón Chain of responsibility para manejar los comandos dados cualquier comando "id" o "valor". (También se mencionó el patrón Strategy). Sin embargo, en su caso, también puede consultar el patrón Command.

Básicamente, en OOP, tratará de utilizar otras soluciones que confiar en los bloques switch, que utilizan un paradigma de programación de procedimientos. Sin embargo, cuándo y cómo usar cualquiera de ellos es algo su decisión. Yo personalmente utilizo a menudo switch bloques cuando se utiliza el patrón de Factory etc.


Una definición de la organización del código es:

  • un paquete es un grupo de clases con API coherant (ex: Collection API en muchos marcos)
  • una clase es un conjunto de funcionalidades coherentes (por ejemplo, una clase Math ...
  • un método es a funcionalidad; debería hacer una cosa y una sola cosa. (por ejemplo: agregar un elemento en una lista puede requerir agrandar esa lista, en cuyo caso el método add se basará en otros métodos para hacerlo y no realizará esa operación, porque no es su contrato).

Por lo tanto, si su instrucción switch realiza diferentes tipos de operaciones, usted es "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).

1

Usa los 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 envío dinámico):

void GameServer::perform_action(shared_ptr<Action> op) { 
    op->execute(); 
} 

los clientes a elegir una acción a realizar, y una vez que lo mandan que la acción ante el servidor para que el servidor no tiene que hacer 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); 
} 
1

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 para el libro 'Código hermoso': "Todos los problemas en la informática pueden resolverse con otro nivel de abstracción"

Utilice un interruptor si lo necesita. Los compiladores son bastante buenos generando código para ellos.

+2

Se supone que debes refactorizar y luego optimizar. Hacerlo al revés no tiene ningún sentido. – Eva

Cuestiones relacionadas