2012-04-28 20 views
7

Quiero evitar el uso de clases etiquetadas y grandes bloques if-else o cambio de declaración y uso el polimorfismo con una jerarquía de clases en su lugar, que creo que es una mejor práctica.creando objetos y polimorfismo

Por ejemplo, algo como el siguiente, donde la elección del método ejecutado depende de un solo campo de un objeto de tipo Actor.

switch(actor.getTagField()) 
{ 
    case 1: actor.act1(); break; 
    case 2: actor.act2(); break; 
    [...] 
} 

se convertiría en

actor.act(); 

y el método acto se aplicaría, en las subclases de Actor.

Sin embargo, la manera más obvia para decidir en tiempo de ejecución, que subclase crear una instancia parece muy similar a la original:

Actor newActor(int type) 
{ 
    switch(type) 
    { 
     case 1: return new Actor1(); 
     case 2: return new Actor2(); 
     [...] 
    } 
} 

así que parece que nada realmente se ha ganado; la lógica acaba de ser movida.

¿Cuál es la mejor manera de hacerlo? La única forma en que se me ocurre es implementar una clase de fábrica para cada subclase de Actor, pero parece bastante engorroso para un problema tan simple.

¿Estoy pensando demasiado? Simplemente parece que no tiene sentido hacer el cambio original si solo hago lo mismo en otro lugar.

Respuesta

4

La pregunta es "si" necesita una fábrica. La fábrica está diseñada para administrar la creación de instancias y no tanto el comportamiento de las instancias relacionadas.

De lo contrario, solo está mirando la herencia básica. Algo como ..

class Actor{ 
    public void act(){ 
    System.out.println("I act.."); 
    } 
} 

class StuntActor extends Actor { 
    public void act(){ 
    System.out.println("I do fancy stunts.."); 
    } 
} 

class VoiceActor extends Actor { 
    public void act(){ 
    System.out.println("I make funny noises.."); 
    } 
} 

Para usar, puede simplemente crear el tipo de actor que necesita directamente.

Actor fred = new Actor(); 
Actor tom = new VoiceActor(); 
Actor sally = new StuntActor(); 

fred.act(); 
tom.act(); 
sally.act(); 

Salida:

I act.. 
I make funny noises.. 
I do fancy stunts.. 

EDIT:

Si necesita centralizar la creación de la Actors..aka vis a fábrica, no será capaz de alejarse de algún tipo de lógica de conmutación, en cuyo caso ...i por lo general a utilizar una enumeración para facilitar la lectura:

public class Actor{ 
    public enum Type{ REGULAR, VOICE, STUNT } 

    public static Actor Create(Actor.Type type){ 
    switch(type) { 
     case VOICE: 
     return new VoiceActor(); 
     case STUNT: 
     return new StuntActor(); 
     case REGULAR: 
     default: 
     return new Actor(); 
    } 
    } 

    public void act(){ 
    System.out.println("I act.."); 
    } 
} 

Uso:

Actor some_actor = Actor.Create(Actor.Type.VOICE); 
some_actor.act(); 

Salida:

I make funny noises.. 
+0

Lo siento, para aclarar, quiero elegir qué subclase para instanciar en tiempo de ejecución, no en tiempo de compilación como en su ejemplo. Esto requiere un interruptor grande o bloque if-else, que quería evitar. Estoy empezando a pensar que no es necesario porque solo se necesitaría una vez en la creación. – flowsnake

+0

Hmm ... en realidad, la fábrica tiene poco que ver con la compilación frente a la instanciación en tiempo de ejecución per se, más bien es la responsabilidad y el control de la creación de Actores. Por ejemplo, si la creación anterior de los Actores sucedió en un Evento, ¿no sería el tiempo de ejecución? –

+0

Lo que estás haciendo es "decidir" centralizar la creación de los actores ... si quieres que los actores vengan de un lugar, en cuyo caso ... no puedes soslayar el cambio ... porque esa es una decisión de El usuario final. –

1

creo que puede hacerlo con Abstract factory pattern ...

Este es un ejemplo:

abstract class Computer { 
    public abstract Parts getRAM(); 
    public abstract Parts getProcessor(); 
    public abstract Parts getMonitor(); 
} 

class Parts { 
    public String specification; 
    public Parts(String specification) { 
     this.specification = specification; 
    } 
    public String getSpecification() { 
     return specification; 
    } 
} 

tenemos dos clases que se extiende Computer

class PC extends Computer { 
    public Parts getRAM() { 
     return new Parts("512 MB"); 
    } 
    public Parts getProcessor() { 
     return new Parts("Celeron"); 
    } 
    public Parts getMonitor() { 
     return new Parts("15 inches"); 
    } 
} 

class Workstation extends Computer { 
    public Parts getRAM() { 
     return new Parts("1 GB"); 
    } 
    public Parts getProcessor() { 
     return new Parts("Intel P 3"); 
    } 
    public Parts getMonitor() { 
     return new Parts("19 inches"); 
    } 
} 

Y por último tenemos ,

public class ComputerType { 
    private Computer comp; 
    public static void main(String[] args) { 
     ComputerType type = new ComputerType(); 
     Computer computer = type.getComputer("Workstation"); 
     System.out.println("Monitor: "+computer.getMonitor().getSpecification()); 
     System.out.println("RAM: "+computer.getRAM().getSpecification()); 
     System.out.println("Processor: "+computer.getProcessor().getSpecification()); 
    }  

    public Computer getComputer(String computerType) { 
     if (computerType.equals("PC")) 
      comp = new PC(); 
     else if(computerType.equals("Workstation")) 
      comp = new Workstation(); 
     return comp; 
    }  
} 
+0

Lo busqué de antemano, pero pensé que probablemente era innecesariamente complicado para lo que quería hacer. No estoy seguro. – flowsnake

2

declaraciones interruptor no son pura maldad. Es realmente duplicación que está buscando eliminar con un mejor diseño. Muchas veces encontrará que la misma instrucción de cambio aparece en diferentes lugares (lejanos) en su código, no necesariamente haciendo lo mismo, sino activando los mismos datos. Al introducir el polimorfismo, los juntáis como métodos diferentes del mismo objeto.

Esto hace dos cosas, primero se reducen varios interruptores a un interruptor dentro de una fábrica y que junta la lógica dispersa que probablemente depende de datos similares. Esa información se convertirá en variables miembro en sus objetos.

También vale la pena señalar que no siempre termina con una declaración de interruptor debajo del capó de su fábrica. Tal vez podría escanear el classpath al inicio y crear un HashMap de tipos que implementan una interfaz. Por ejemplo, considere una implementación de un protocolo de socket como SMTP. Podría tener objetos llamados HeloCommand, MailFromCommand, etc. ... y encontrar el objeto correcto para manejar el mensaje haciendo coincidir el comando socket con el nombre de la clase.

+0

Realmente consideré hacer algo así, pero luego comencé a leer sobre cómo la reflexión debería ser un último recurso. – flowsnake

+0

La reflexión es una herramienta poderosa y hay formas de usarla sin sacrificar el rendimiento. El ejemplo que describí en mi respuesta solo usaría el reflejo una vez en el inicio y luego utilizaría un 'HashMap'. –