¿Cuáles son las formas de eliminar el uso del interruptor en el código?
design-patterns
Mariano
fuente
fuente
Respuestas:
Las declaraciones de cambio no son un antipatrón per se, pero si está codificando orientado a objetos, debe considerar si el uso de un cambio se resuelve mejor con polimorfismo en lugar de usar una declaración de cambio.
Con polimorfismo, esto:
se convierte en esto:
fuente
typeof
, y esta respuesta no sugiere formas o razones para evitar las declaraciones de cambio en otras situaciones.Ver el olor de las declaraciones del interruptor :
Tanto Refactoring como Refactoring to Patterns tienen enfoques para resolver esto.
Si su (pseudo) código se ve así:
Este código viola el Principio de Abierto Cerrado y es frágil para cada nuevo tipo de código de acción que se presente. Para remediar esto, podría introducir un objeto 'Comando':
Si su (pseudo) código se ve así:
Entonces podría introducir un objeto 'Estado'.
Espero que esto ayude.
fuente
Map<Integer, Command>
no necesitaría un interruptor?Un switch es un patrón, ya sea implementado con una declaración de switch, si es otra cadena, tabla de búsqueda, polimorfismo opulento, coincidencia de patrones u otra cosa.
¿Desea eliminar el uso de la " declaración de cambio " o el " patrón de cambio "? El primero se puede eliminar, el segundo, solo si se puede usar otro patrón / algoritmo, y la mayoría de las veces eso no es posible o no es un mejor enfoque para hacerlo.
Si desea eliminar la declaración de cambio del código, la primera pregunta que debe hacerse es dónde tiene sentido eliminar la declaración de cambio y utilizar alguna otra técnica. Lamentablemente, la respuesta a esta pregunta es específica del dominio.
Y recuerde que los compiladores pueden hacer varias optimizaciones para cambiar las declaraciones. Entonces, por ejemplo, si desea procesar el mensaje de manera eficiente, una declaración de cambio es el camino a seguir. Pero, por otro lado, ejecutar reglas de negocios basadas en una declaración de cambio probablemente no sea la mejor manera de hacerlo y la aplicación debe ser reorganizada.
Aquí hay algunas alternativas para cambiar la declaración:
fuente
El cambio en sí mismo no es tan malo, pero si tiene muchos "cambios" o "si / más" en los objetos en sus métodos, puede ser una señal de que su diseño es un poco "procesal" y que sus objetos son solo un valor cubos Mueva la lógica a sus objetos, invoque un método en sus objetos y deje que ellos decidan cómo responder.
fuente
Creo que la mejor manera es usar un buen mapa. Usando un diccionario puede asignar casi cualquier entrada a algún otro valor / objeto / función.
su código se vería algo (psuedo) como este:
fuente
Todos aman los ENORMES
if else
bloques. ¡Tan fácil de leer! Sin embargo, tengo curiosidad por saber por qué querría eliminar las declaraciones de cambio. Si necesita una declaración de cambio, probablemente necesite una declaración de cambio. En serio, diría que depende de lo que esté haciendo el código. Si todo lo que está haciendo el conmutador es llamar a funciones (por ejemplo), podría pasar punteros de función. Si es una mejor solución es discutible.El lenguaje es un factor importante aquí también, creo.
fuente
Creo que lo que estás buscando es el Patrón de estrategia.
Esto se puede implementar de varias maneras, que se han mencionado en otras respuestas a esta pregunta, como:
fuente
switch
sería bueno reemplazar las declaraciones si se encuentra agregando nuevos estados o un nuevo comportamiento a las declaraciones:Agregar un nuevo comportamiento requiere copiar el
switch
, y agregar nuevos estados significa agregar otrocase
a cadaswitch
declaración.En Java, solo puede cambiar un número muy limitado de tipos primitivos cuyos valores conoce en tiempo de ejecución. Esto presenta un problema en sí mismo: los estados se representan como números o caracteres mágicos.
La coincidencia de patrones y múltiples
if - else
bloques se pueden usar, aunque realmente tienen los mismos problemas al agregar nuevos comportamientos y nuevos estados.La solución que otros han sugerido como "polimorfismo" es una instancia del patrón de Estado :
Reemplace cada uno de los estados con su propia clase. Cada comportamiento tiene su propio método en la clase:
Cada vez que agrega un nuevo estado, debe agregar una nueva implementación de la
IState
interfaz. En unswitch
mundo, estarías agregando uncase
a cada unoswitch
.Cada vez que agrega un nuevo comportamiento, debe agregar un nuevo método a la
IState
interfaz y a cada una de las implementaciones. Esta es la misma carga que antes, aunque ahora el compilador verificará que tenga implementaciones del nuevo comportamiento en cada estado preexistente.Otros ya han dicho que esto puede ser demasiado pesado, por lo que, por supuesto, hay un punto al que se llega donde se mueve de uno a otro. Personalmente, la segunda vez que escribo un interruptor es el punto en el que refactorizo.
fuente
si más
Sin embargo, rechazo la premisa de que el cambio es inherentemente malo.
fuente
Bueno, por un lado, no sabía que usar el interruptor era un anti patrón.
En segundo lugar, el interruptor siempre se puede reemplazar con declaraciones if / else if.
fuente
¿Por qué quieres? En manos de un buen compilador, una declaración de cambio puede ser mucho más eficiente que los bloques if / else (además de ser más fácil de leer), y es probable que solo los cambios más grandes se aceleren si se reemplazan por algún tipo de la estructura de datos de búsqueda indirecta.
fuente
'cambiar' es solo una construcción de lenguaje y todas las construcciones de lenguaje pueden considerarse como herramientas para hacer un trabajo. Al igual que con las herramientas reales, algunas herramientas se adaptan mejor a una tarea que a otra (no usaría un martillo para colocar un gancho de imagen). La parte importante es cómo se define 'hacer el trabajo'. ¿Necesita ser mantenible, necesita ser rápido, necesita escalar, necesita ser extensible y así sucesivamente?
En cada punto del proceso de programación, generalmente hay una gama de construcciones y patrones que se pueden usar: un interruptor, una secuencia if-else-if, funciones virtuales, tablas de salto, mapas con punteros de función, etc. Con experiencia, un programador sabrá instintivamente la herramienta adecuada para usar en una situación dada.
Se debe suponer que cualquier persona que mantenga o revise el código es al menos tan hábil como el autor original para que cualquier construcción pueda usarse de manera segura.
fuente
Si el interruptor está ahí para distinguir entre varios tipos de objetos, probablemente te faltan algunas clases para describir con precisión esos objetos, o algunos métodos virtuales ...
fuente
Para C ++
Si se refiere, es decir, a un AbstractFactory, creo que un método registerCreatorFunc (..) generalmente es mejor que requerir agregar un caso para cada una de las declaraciones "nuevas" que se necesitan. Luego, dejar que todas las clases creen y registren una función creator (..) que se puede implementar fácilmente con una macro (si me atrevo a mencionar). Creo que este es un enfoque común que hacen muchos marcos. Lo vi por primera vez en ET ++ y creo que muchos marcos que requieren una macro DECL e IMPL lo usan.
fuente
En un lenguaje de procedimiento, como C, el cambio será mejor que cualquiera de las alternativas.
En un lenguaje orientado a objetos, casi siempre hay otras alternativas disponibles que utilizan mejor la estructura del objeto, particularmente el polimorfismo.
El problema con las declaraciones de cambio surge cuando se producen varios bloques de cambio muy similares en varios lugares de la aplicación, y se necesita agregar soporte para un nuevo valor. Es bastante común que un desarrollador olvide agregar soporte para el nuevo valor a uno de los bloques de interruptores dispersos por la aplicación.
Con el polimorfismo, una nueva clase reemplaza el nuevo valor y el nuevo comportamiento se agrega como parte de la adición de la nueva clase. El comportamiento en estos puntos de cambio se hereda de la superclase, se anula para proporcionar un nuevo comportamiento o se implementa para evitar un error del compilador cuando el supermétodo es abstracto.
Donde no esté ocurriendo un polimorfismo obvio, puede valer la pena implementar el patrón de Estrategia .
Pero si tu alternativa es un gran bloque SI ... ENTONCES ... OTRO, entonces olvídalo.
fuente
Use un lenguaje que no venga con una declaración de cambio incorporada. Perl 5 viene a la mente.
En serio, ¿por qué querrías evitarlo? Y si tiene buenas razones para evitarlo, ¿por qué no simplemente evitarlo entonces?
fuente
Los punteros de función son una forma de reemplazar una declaración de interruptor enorme y gruesa, son especialmente buenos en lenguajes donde puede capturar funciones por sus nombres y hacer cosas con ellas.
Por supuesto, no debe forzar las declaraciones de cambio fuera de su código, y siempre existe la posibilidad de que lo esté haciendo todo mal, lo que resulta en estúpidos trozos de código redundantes. (Esto es inevitable a veces, pero un buen lenguaje debería permitirle eliminar la redundancia mientras se mantiene limpio).
Este es un gran ejemplo de divide y vencerás:
Digamos que tiene un intérprete de algún tipo.
En cambio, puedes usar esto:
Tenga en cuenta que no sé cómo eliminar la redundancia de opcode_table en C. Quizás debería hacer una pregunta al respecto. :)
fuente
La respuesta más obvia, independiente del lenguaje, es usar una serie de 'si'.
Si el lenguaje que está utilizando tiene punteros de función (C) o tiene funciones que son valores de primera clase (Lua), puede obtener resultados similares a un "interruptor" usando una matriz (o una lista) de funciones (punteros a).
Debería ser más específico en el idioma si desea mejores respuestas.
fuente
Las declaraciones de cambio a menudo se pueden reemplazar por un buen diseño OO.
Por ejemplo, tiene una clase de cuenta y está utilizando una declaración de cambio para realizar un cálculo diferente según el tipo de cuenta.
Sugeriría que esto se reemplace por una serie de clases de cuenta, que representan los diferentes tipos de cuenta, y que todas implementen una interfaz de Cuenta.
El cambio se vuelve innecesario, ya que puede tratar todos los tipos de cuentas de la misma manera y, gracias al polimorfismo, se ejecutará el cálculo apropiado para el tipo de cuenta.
fuente
¡Depende de por qué quieras reemplazarlo!
Muchos intérpretes usan 'gotos computados' en lugar de declaraciones de cambio para la ejecución del código de operación.
Lo que extraño sobre el interruptor C / C ++ es el Pascal 'in' y los rangos. También desearía poder encender las cuerdas. Pero estos, aunque triviales para un compilador para comer, son un trabajo duro cuando se hace usando estructuras e iteradores y cosas. Entonces, por el contrario, hay muchas cosas que desearía poder reemplazar con un interruptor, ¡si solo el interruptor de C () fuera más flexible!
fuente
El cambio no es una buena manera de hacerlo, ya que rompe el Open Close Principal. Así es como lo hago.
Y aquí está cómo usarlo (tomando su código):
Básicamente, lo que estamos haciendo es delegar la responsabilidad a la clase infantil en lugar de que los padres decidan qué hacer con los niños.
Es posible que también desee leer sobre el "Principio de sustitución de Liskov".
fuente
En JavaScript usando la matriz asociativa:
esto:
se convierte en esto:
Cortesía
fuente
Otro voto para si / si no. No soy un gran admirador de las declaraciones de casos o cambios porque hay algunas personas que no las usan. El código es menos legible si usa mayúsculas o minúsculas. Quizás no sea menos legible para usted, sino para aquellos que nunca han necesitado usar el comando.
Lo mismo ocurre con las fábricas de objetos.
Los bloques If / else son una construcción simple que todos obtienen. Hay algunas cosas que puede hacer para asegurarse de no causar problemas.
En primer lugar, no intente sangrar las declaraciones más de un par de veces. Si te encuentras sangrando, lo estás haciendo mal.
Es realmente malo, haz esto en su lugar.
La optimización sea condenada. No hace mucha diferencia en la velocidad de su código.
En segundo lugar, no soy reacio a salir de un Bloque If siempre que haya suficientes declaraciones de interrupción dispersas a través del bloque de código particular para que sea obvio
EDITAR : en Switch y por qué creo que es difícil asimilar:
Aquí hay un ejemplo de una declaración de cambio ...
Para mí, el problema aquí es que las estructuras de control normales que se aplican en lenguajes similares a C se han roto por completo. Existe una regla general de que si desea colocar más de una línea de código dentro de una estructura de control, use llaves o una declaración de inicio / fin.
p.ej
Para mí (y puede corregirme si me equivoco), la declaración de Caso arroja esta regla por la ventana. Un bloque de código ejecutado condicionalmente no se coloca dentro de una estructura de inicio / fin. Debido a esto, creo que Case es conceptualmente diferente para no ser utilizado.
Espero que eso conteste tus preguntas.
fuente