2011-02-01 20 views
19

"Reemplazar condicionalmente por polimorfismo" es elegante solo cuando el tipo de objeto para el que está haciendo conmutar/si la instrucción para ya está seleccionada. Como ejemplo, tengo una aplicación web que lee un parámetro de cadena de consulta llamado "acción". La acción puede tener valores de "vista", "editar", "ordenar", etc. Entonces, ¿cómo implemento esto con polimorfismo? Bueno, puedo crear una clase abstracta llamada BaseAction, y deriva ViewAction, EditAction y SortAction de ella. Pero, ¿no necesito un condicional para decidir qué sabor de tipo BaseAction crear? No veo cómo puedes reemplazar por completo los condicionales con polimorfismo. En todo caso, los condicionales solo se empujan hacia la parte superior de la cadena.Reemplazar condicionalmente por polimorfismo: agradable en teoría pero no práctico

EDIT:

public abstract class BaseAction 
{ 
    public abstract void doSomething(); 
} 

public class ViewAction : BaseAction 
{ 
    public override void doSomething() { // perform a view action here... } 
} 

public class EditAction : BaseAction 
{ 
    public override void doSomething() { // perform an edit action here... } 
} 

public class SortAction : BaseAction 
{ 
    public override void doSomething() { // perform a sort action here... } 
} 


string action = "view"; // suppose user can pass either "view", "edit", or "sort" strings to you. 
BaseAction theAction = null; 

switch (action) 
{ 
    case "view": 
     theAction = new ViewAction(); 
     break; 

    case "edit": 
     theAction = new EditAction(); 
     break; 

    case "sort": 
     theAction = new SortAction(); 
     break; 
} 

theAction.doSomething(); // So I don't need conditionals here, but I still need it to decide which BaseAction type to instantiate first. There's no way to completely get rid of the conditionals. 
+0

por favor, entregue más código si está buscando un tutorial de polimorfismo que puede encontrar en cualquier lugar de la web. Por favor, proporciona un ejemplo. – amirouche

+0

No se pudo agregar todo el código aquí. Proporcioné un ejemplo a continuación (busque Charles) – Charles

+0

Puede reemplazar las condiciones restantes utilizando la API de reflexión. Determine en el tiempo de ejecución qué tipo de acción de base desea crear una instancia dependiendo de la cadena. hay una caída hacia abajo a esto, su cadena debe coincidir con el nombre exacto de su acción base. También puede usar un mapa hash para asignar una cadena con acción base y seleccionar en tiempo de ejecución. –

Respuesta

23

Tienes razón - "los condicionales están siendo empujados hasta la parte superior de la cadena" - pero no hay "solo" al respecto. Es muy poderoso. Como dice @thkala, simplemente haga la elección una vez; de allí en adelante, el objeto sabe cómo hacer sus negocios. El enfoque que describes, BaseAction, ViewAction y el resto, es una buena forma de hacerlo. Pruébelo y vea cuánto más limpio se vuelve su código.

Cuando tienes un método de fábrica que toma una cadena como "Ver" y devuelve una Acción, y la llamas, has aislado tu condicionalidad. Eso es genial. Y no puedes apreciar adecuadamente la potencia hasta que lo hayas probado, ¡así que pruébalo!

+0

Gracias, eso tiene mucho sentido. – Charles

7

Algunas cosas a tener en cuenta:

  • Sólo una instancia de cada objeto vez. Una vez que hagas eso, no se necesitarán más condicionales con respecto a su tipo.

  • Incluso en instancias únicas, ¿cuántos condicionales eliminarías si usaras subclases? Código usando condicionales como esto es bastante propenso a ser completa de la misma exacta condicional y otra vez y otra vez ...

  • ¿Qué pasa cuando se necesita un valor foo Acción en el futuro? ¿Cuántos lugares tendrá que modificar?

  • ¿Qué sucede si necesita un bar que es ligeramente diferente de foo? Con las clases, simplemente hereda BarAction del FooAction, anulando lo único que necesita cambiar.

En el código orientado a largo plazo objeto es generalmente más fácil de mantener que el código de procedimiento - los gurús no tienen un problema con cualquiera, pero para el resto de nosotros no es una diferencia.

+0

Puedo ver los beneficios en sentido descendente. Pero, de nuevo, todavía necesito una caja de conmutadores en alguna parte para decidir qué tipo crear. – Charles

3

Su ejemplo no requiere polimorfismo, y puede no ser aconsejable. Sin embargo, la idea original de reemplazar la lógica condicional por el envío polimórfico es sólida.

Aquí está la diferencia: en su ejemplo tiene un pequeño conjunto de acciones fijas (y predeterminadas). Además, las acciones no están fuertemente relacionadas en el sentido de que las acciones de 'ordenar' y 'editar' tienen poco en común. El polimorfismo está sobre-diseñando su solución.

Por otro lado, si tiene muchos objetos con un comportamiento especializado para una noción común, el polimorfismo es exactamente lo que desea. Por ejemplo, en un juego puede haber muchos objetos que el jugador puede "activar", pero cada uno responde de manera diferente. Podría implementar esto con condiciones complejas (o más probablemente una declaración de cambio), pero el polimorfismo sería mejor. El polimorfismo le permite introducir nuevos objetos y comportamientos que no formaban parte de su diseño original (pero encajaban en su ethos).

En su ejemplo, aún sería una buena idea abstraerse sobre los objetos que admiten las acciones de visualización/edición/ordenación, pero quizás no abstraer estas acciones por sí mismos. Aquí hay una prueba: ¿alguna vez querría poner esas acciones en una colección? Probablemente no, pero es posible que tenga una lista de los objetos que los respaldan.

3

Hay varias maneras de traducir una cadena de entrada a un objeto de un tipo determinado y un condicional es definitivamente una de ellas. Dependiendo del lenguaje de implementación, también podría ser posible usar una instrucción switch que permita especificar cadenas esperadas como índices y crear o recuperar un objeto del tipo correspondiente. Todavía hay una mejor manera de hacerlo.

Una tabla de búsqueda se puede utilizar para asignar cadenas de entrada a los objetos necesarios:

action = table.lookup (action_name); // Retrieve an action by its name 
if (action == null) ...    // No matching action is found 

El código de inicialización se haría cargo de la creación de los objetos necesarios, por ejemplo

table ["edit"] = new EditAction(); 
table ["view"] = new ViewAction(); 
... 

Ésta es la Esquema básico que puede ampliarse para abarcar más detalles, como argumentos adicionales de los objetos de acción, normalización de los nombres de las acciones antes de usarlos para la búsqueda de tablas, sustitución de una tabla por una matriz simple utilizando enteros en lugar de cadenas para identificar las acciones solicitadas , etc.

+0

También prefiero búsquedas/diccionarios/hashes para cambiar las declaraciones. –

1
public abstract class BaseAction 
{ 
    public abstract void doSomething(); 
} 

public class ViewAction : BaseAction 
{ 
    public override void doSomething() { // perform a view action here... } 
} 

public class EditAction : BaseAction 
{ 
    public override void doSomething() { // perform an edit action here... } 
} 

public class SortAction : BaseAction 
{ 
    public override void doSomething() { // perform a sort action here... } 
} 


string action = "view"; // suppose user can pass either 
         // "view", "edit", or "sort" strings to you. 
BaseAction theAction = null; 

switch (action) 
{ 
    case "view": 
     theAction = new ViewAction(); 
     break; 

    case "edit": 
     theAction = new EditAction(); 
     break; 

    case "sort": 
     theAction = new SortAction(); 
     break; 
} 

theAction.doSomething(); 

Así que no necesito condicionales aquí, pero todavía lo necesito para decidir qué tipo de BaseAction crear primero. No hay forma de deshacerse completamente de los condicionales.

0

El polimorfismo es un método de unión. Es un caso especial de lo que se conoce como "Modelo de Objeto". Los modelos de objetos se usan para manipular sistemas complejos, como circuitos o dibujos. Considere algo almacenado/ordenado en formato de texto: elemento "A", conectado al elemento "B" y "C". Ahora necesita saber qué está conectado a A. Un tipo puede decir que no voy a crear un Modelo de Objetos para esto porque puedo contarlo mientras analizo, pase único. En este caso, puede estar en lo cierto, puede escaparse sin un modelo de objeto. Pero, ¿y si necesita hacer muchas manipulaciones complejas con diseño importado? ¿Lo manipularás en formato de texto o enviar mensajes invocando métodos java y haciendo referencia a objetos java es más conveniente? Es por eso que se mencionó que necesita hacer la traducción solo una vez.

1

He estado pensando en este problema probablemente más que el resto de desarrolladores que conocí. La mayoría de ellos tienen un costo totalmente despreocupado de mantener largas sentencias anónimas if-else o casos de conmutación. Entiendo totalmente tu problema al aplicar una solución llamada "Reemplazar condicionalmente por polimorfismo" en tu caso. Has notado con éxito que el polimorfismo funciona siempre que el objeto ya esté seleccionado. También se ha dicho en este dibujo que este problema puede reducirse a asociación [clave] -> [clase]. Aquí está, por ejemplo, la implementación AS3 de la solución.

private var _mapping:Dictionary; 
private function map():void 
{ 
    _mapping["view"] = new ViewAction(); 
    _mapping["edit"] = new EditAction(); 
    _mapping["sort"] = new SortAction(); 
} 

private function getAction(key:String):BaseAction 
{ 
    return _mapping[key] as BaseAction; 
} 

en ejecución que le gustaría estar en:

public function run(action:String):void 
{ 
    var selectedAction:BaseAction = _mapping[action]; 
    selectedAction.apply(); 
} 

En ActionScript3 hay una función global llamada getDefinitionByName (clave: String): Class.La idea es usar los valores clave para que coincidan con los nombres de las clases que representan la solución a su condición. En su caso, deberá cambiar "ver" a "ViewAction", "editar" a "EditAction" y "ordenar" a "SortAtion". No es necesario memorizar nada utilizando tablas de búsqueda. La función de ejecución se verá así:

public function run(action:Script):void 
{ 
    var class:Class = getDefintionByName(action); 
    var selectedAction:BaseAction = new class(); 
    selectedAction.apply(); 
} 

Desafortunadamente compilación suelta comprobar con esta solución, pero se obtiene flexibilidad para agregar nuevas acciones. Si crea una nueva clave, lo único que debe hacer es crear una clase apropiada que la administre.

Por favor, deje un comentario incluso si no está de acuerdo conmigo.

7

Aunque la última respuesta fue hace un año, me gustaría hacer algunas revisiones/comentarios sobre este tema.

revisión Respuestas

Estoy de acuerdo con @CarlManaster acerca de la codificación de la declaración switch una vez para evitar todos los problemas conocidos de tratar con código duplicado, en este caso relacionado con los condicionales (algunos de ellos mencionados por @thkala).

No creo que el enfoque propuesto por @ KonradSzałwiński o @AlexanderKogtenkov se adapte a este escenario por dos razones:

primer lugar, desde el problema que has descrito, que no es necesario cambiar dinámicamente la asignación entre el nombre de una acción y la instancia de una acción que lo maneja.

Observe que estas soluciones permiten hacer eso (simplemente asignando un nombre de acción a una nueva instancia de acción), mientras que la solución estática basada en conmutadores no (las asignaciones están codificadas). Además, todavía necesitará un condicional para verificar si una determinada clave se define en la tabla de asignación, si no se debe tomar una acción (la default parte de una declaración de conmutación).

En segundo lugar, en este ejemplo particular, dictionaries son implementaciones realmente ocultas de la instrucción switch. Aún más, podría ser más fácil leer/comprender la instrucción switch con la cláusula predeterminada que tener que ejecutar mentalmente el código que devuelve el objeto de manejo de la tabla de asignación, incluido el manejo de una clave no definida.

Hay una manera que usted puede deshacerse de todos los condicionales, incluyendo la instrucción switch:

Extracción de la instrucción switch (no utilizar los condicionales en absoluto)

Cómo crear el objeto de la acción derecha desde el nombre de la acción?

Voy a ser independiente del idioma por lo que esta respuesta no llega que larga, pero el truco es darse cuenta de clases también son objetos.

Si ya ha definido una jerarquía polimórfica, no tiene sentido hacer referencia a una subclase concreta de BaseAction: ¿por qué no pedirle que devuelva la instancia correcta manejando una acción por su nombre?

Normalmente se implementa mediante la misma instrucción de cambio que había escrito (por ejemplo, un método de fábrica) ...pero ¿qué pasa con esto:

public class BaseAction { 

    //I'm using this notation to write a class method 
    public static handlingByName(anActionName) { 
     subclasses = this.concreteSubclasses() 

     handlingClass = subclasses.detect(x => x.handlesByName(anActionName)); 

     return new handlingClass(); 
    } 
} 

Entonces, ¿qué está haciendo ese método?

Primero, recupera todas las subclases concretas de esto (que apunta a BaseAction). En su ejemplo, obtendría una colección con ViewAction, EditAction y SortAction.

Observe que dije subclases concretas, no todas las subclases. Si la jerarquía es más profunda, las subclases concretas siempre serán las que se encuentran en la parte inferior de la jerarquía (hoja). Eso es porque son los únicos que se supone que no son abstractos y proporcionan una implementación real.

En segundo lugar, obtenga la primera subclase que responda si puede o no manejar una acción por su nombre (estoy usando una notación aromatizada lambda/closure). Un ejemplo de implementación del método handlesByName clase para ViewAction se vería así:

public static class ViewAction { 

    public static bool handlesByName(anActionName) { 
     return anActionName == 'view' 
    } 

} 

En tercer lugar, enviamos el mensaje de nuevo a la clase que se encarga de la acción, creando una instancia del mismo.

Por supuesto, tiene que tratar con el caso cuando ninguna de las subclases maneja la acción por su nombre. Muchos lenguajes de programación, incluidos Smalltalk y Ruby, permiten pasar el método de detección a una segunda lambda/cierre que solo se evaluará si ninguna de las subclases coincide con los criterios. Además, tendrá que tratar con el caso más de una subclase maneja la acción por su nombre (probablemente, uno de estos métodos fue codificado de manera incorrecta).

Conclusión

Una ventaja de este enfoque es que las nuevas acciones pueden ser apoyadas por escrito (y no modificar) el código existente: acaba de crear una nueva subclase de BaseAction e implementar el método handlesByName clase correctamente. Es efectivamente compatible con agregar una nueva característica al agregar un nuevo concepto, sin modificar la impedancia existente. Está claro que, si la nueva característica requiere que se agregue un nuevo método polimórfico a la jerarquía, se necesitarán cambios.

Además, puede proporcionar a los desarrolladores que utilizan los comentarios de su sistema: "La acción proporcionada no se trata mediante ninguna subclase de BaseAction, cree una nueva subclase e implemente los métodos abstractos". Para mí, el hecho de que el modelo mismo te diga qué es lo que está mal (en lugar de tratar de ejecutar mentalmente una tabla de consulta) agrega valor y aclara las instrucciones sobre lo que se debe hacer.

Sí, esto podría sonar sobrediseño. Mantenga una mente abierta y descubra que si una solución está sobrediseñada o no tiene que ver, entre otras cosas, con la cultura de desarrollo del lenguaje de programación particular que está utilizando. Por ejemplo, los chicos de .NET probablemente no lo usen porque .NET no permite tratar las clases como objetos reales, mientras que, por otro lado, esa solución se usa en culturas Smalltalk/Ruby.

Finalmente, use el sentido común y el gusto para determinar de antemano si una técnica en particular realmente resuelve su problema antes de usarlo. Es tentador que sí, pero deben evaluarse todos los intercambios (cultura, antigüedad de los desarrolladores, resistencia al cambio, apertura de la mente, etc.).

0

Puede almacenar cadena y el tipo de acción correspondiente en algún lugar del mapa hash.

public abstract class BaseAction 
{ 
    public abstract void doSomething(); 
} 

public class ViewAction : BaseAction 
{ 
    public override void doSomething() { // perform a view action here... } 
} 

public class EditAction : BaseAction 
{ 
    public override void doSomething() { // perform an edit action here... } 
} 

public class SortAction : BaseAction 
{ 
    public override void doSomething() { // perform a sort action here... } 
} 


string action = "view"; // suppose user can pass either 
         // "view", "edit", or "sort" strings to you. 
BaseAction theAction = null; 

theAction = actionMap.get(action); // decide at runtime, no conditions 
theAction.doSomething(); 
Cuestiones relacionadas