2010-03-31 16 views
6

conjetura bien esta pregunta se parece mucho a:Refactor código java

What is the best way to replace or substitute if..else if..else trees in programs?

considerar esta pregunta cerrada!


me gustaría refactorizar código que se ve algo como esto:

String input; // input from client socket. 
if (input.equals(x)) { 
    doX(); 
} else if (input.equals(y)) { 
    doY(); 
} else { 
    unknown_command(); 
} 

Es el código que comprueba la entrada de la toma para realizar alguna acción, pero no me gusta la construcción if else porque cada Cuando se agrega un nuevo comando al servidor (código), se debe agregar un nuevo if else que es feo. Además, al eliminar un comando, se debe modificar if else.

+0

'Mapa'? En realidad eso solo mueve el problema. Sin embargo, pondría las funciones en una instancia diferente, y tal vez deslice en una 'interfaz'. –

+2

(Las líneas Psst 2 y 4 faltan un paréntesis de cierre). –

+0

Oops :). Tienes razón. Los reparé. – Alfred

Respuesta

8

Recoge esos comandos en un Map<String, Command> donde Command es un interface con un método execute().

Map<String, Command> commands = new HashMap<String, Command>(); 
// Fill it with concrete Command implementations with `x`, `y` and so on as keys. 

// Then do: 
Command command = commands.get(input); 
if (command != null) { 
    command.execute(); 
} else { 
    // unknown command. 
} 

Para obtener un paso más allá, usted podría considerar para llenar el mapa de forma dinámica mediante el escaneo de las clases que implementan una interfaz específica (Command en este caso) o una anotación específica en la ruta de clase. Google Reflections puede ayudar mucho en esto.

Actualización (de los comentarios) También puede considerar combinar el answer of Instantsoup con mi respuesta. Durante el método buildExecutor(), primero debe obtener el comando de un Map y si el comando no existe en Map, a continuación, tratar de cargar la clase asociada y la puso en el Map. Tipo de carga floja. Esto es más eficiente que escanear todo el classpath como en mi respuesta y crearlo cada vez como en la respuesta de Instantsoup.

+0

Pero entonces tengo que modificar el mapa cada vez que agregue/elimine un nuevo comando? ¿No puedo hacer esto dinámicamente o algo así? – Alfred

+0

Sí, me di cuenta de eso también y lo edité justo antes de ver tu comentario :) – BalusC

+0

@Alfred. Podrías probar la reflexión, pero difícilmente es la mejor opción. Vaya con el mapa, guárdelo en un lugar seguro donde sea fácil agregar nuevos comandos. – Tom

3

Una forma podría ser tener una interfaz ICommand que es el contrato general para un comando, por ejemplo:

public interface ICommand { 
    /** @param context The command's execution context */ 
    public void execute(final Object context); 
    public String getKeyword(); 
} 

Y entonces usted podría utilizar el mecanismo de Java SPI para auto-descubrir sus diversas implementaciones y registrarlos en a Map<String,ICommand> y luego haz knownCommandsMap.get(input).execute(ctx) o algo por el estilo.

Esta práctica le permite desacoplar su servicio de implementaciones de mandatos, con eficacia haciendo los enchufable.

El registro de una clase de implementación con el SPI se hace agregando un archivo denominado como el nombre completo de su clase ICommand (de modo que si está en el archivo ficticio el archivo va a ser META-INF/dummy.ICommand dentro de su classpath), y luego ' ll cargar y registrarlos como:

final ServiceLoader<ICommand> spi = ServiceLoader.load(ICommand.class); 
for(final ICommand commandImpl : spi) 
    knownCommandsMap.put(commandImpl.getKeyword(), commandImpl); 
+0

esto solo está disponible en JDK 6 –

+0

¿Podría explicar la parte de SPI un poco mejor? – Alfred

+0

@fuzzy lollipop: En realidad, ya estaba en JDK 5, pero en un paquete no público (sun.misc ...). Debería haber sido parte de JDK 5 al principio. – Romain

3

¿Qué hay de las interfaces, una fábrica y un poco de reflexión? Todavía tendrá que manejar excepciones en malas entradas, pero siempre tendrá que hacer esto. Con este método, solo agrega una nueva implementación de Executor para una nueva entrada.

public class ExecutorFactory 
{ 
    public static Executor buildExecutor(String input) throws Exception 
    { 
     Class<Executor> forName = (Class<Executor>) Class.forName(input); 
     return (Executor) executorClass.newInstance(); 
    } 
} 

public interface Executor 
{ 
    public void execute(); 
} 


public class InputA implements Executor 
{ 
    public void execute() 
    { 
     // do A stuff 
    } 
} 

public class InputB implements Executor 
{ 
    public void execute() 
    { 
     // do B stuff 
    } 
} 

Su ejemplo de código se convierte entonces en

String input; 
ExecutorFactory.buildExecutor(input).execute(); 
+1

También es una buena idea, solo tiene el costo de crear una nueva instancia cada vez. – BalusC

+0

Sí, también me gusta esta idea. – Alfred

+1

¿De dónde viene el nombre del ejecutor?Si se trata de una entrada de cliente/usuario, un usuario malintencionado podría instanciar cualquier clase en el sistema. Si la inicialización de la clase cambia de estado en el sistema, entonces el atacante obtendría cierto nivel de control sobre el sistema. Entonces habría una condición de carrera entre la instanciación y la excepción lanzada (suponiendo que la clase no es una subclase de Ejecutor), durante la cual el atacante podría aprovechar cualquier funcionalidad abierta por la otra clase ... – atk

2

Construyendo el Patten de comandos en una clase de enumeración puede reducir algunos de los código repetitivo. Vamos a suponer que x es input.equals(x) en "XX" e y en input.equals(y) es "AA"

enum Commands { 
    XX { 
    public void execute() { doX(); }   
    }, 
    YY { 
    public void execute() { doY(); }   
    }; 

    public abstract void execute(); 
} 

String input = ...; // Get it from somewhere 

try { 
    Commands.valueOf(input).execute(); 
} 
catch(IllegalArgumentException e) { 
    unknown_command(); 
} 
+0

También es una buena idea, pero esto está muy unido. Ya no puedes proporcionar comandos desde "afuera". – BalusC

1

Usted dice que está procesando la entrada de un zócalo. ¿Cuánta entrada? ¿Qué tan complejo es? ¿Qué tan estructurado es?

Dependiendo de las respuestas a esas preguntas, es mejor escribir una gramática y dejar que un generador de analizador (por ejemplo, ANTLR) genere el código de procesamiento de entrada.

+0

Solo un protocolo simple. Por ejemplo, memcached. – Alfred

+1

@Alfred: en el caso de memcached, que es un protocolo muy simple que tiene media docena de operaciones y es poco probable que cambie, usaría una construcción if-else. No hay ninguna razón para hacer que su modo de código sea complejo solo para "orientarlo". Sin embargo, crearía un objeto para envolver el comando y/o respuesta, y manejaría todo el análisis sintáctico. Y probablemente cree una enumeración para representar el comando (que convertiría la cadena if-else en un interruptor). – Anon

+1

Si quisiera poder sustituir comportamientos fácilmente, podría usar el patrón de Método de plantilla, crear métodos abstractos para cada una de las operaciones y hacer que mi aplicación creara instancias de subclases. – Anon

Cuestiones relacionadas