2010-03-31 8 views
6

Ok Vermutung diese Frage sieht viel wie:Umgestalten Java-Code

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

betrachten diese Frage GESCHLOSSEN!


Ich möchte Code Refactoring, die etwa wie folgt aussieht:

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

Es ist Code, der eine Eingabe von Socket überprüft, eine Aktion auszuführen, aber ich weiß nicht wie die if else Konstruktion, weil jeder Wenn ein neuer Befehl zum Server (Code) hinzugefügt wird, muss ein neuer Befehl hinzugefügt werden, der sonst hässlich ist. Auch beim Löschen eines Befehls muss die if else geändert werden.

+0

'Karte'? Das bewegt eigentlich nur das Problem. Ich würde die Funktionen jedoch in eine andere Instanz einfügen und vielleicht eine "Schnittstelle" einschieben. –

+2

(Psst Zeilen 2 und 4 fehlt eine schließende Klammer.) –

+0

Hoppla :). Du hast recht. Ich habe es repariert. – Alfred

Antwort

8

Sammeln Sie diese Befehle in einer Map<String, Command>, wobei Command eine interface mit einer execute() Methode ist.

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. 
} 

Um einen Schritt weiter kommen soll, können Sie die Karte dynamisch durch Scannen für die Klassen füllen eine bestimmte Schnittstelle (Command in diesem Fall) oder eine bestimmte Anmerkung im Classpath zu implementieren. Google Reflections kann viel dabei helfen.

Update (aus den Kommentaren) Sie können auch die Kombination der answer of Instantsoup mit meiner Antwort in Betracht ziehen. Rufen Sie während der Methode buildExecutor() zuerst den Befehl von einer Map und wenn der Befehl in Map nicht existiert, dann versuchen Sie, die zugehörige Klasse zu laden und in die Map. Eine Art faules Laden. Dies ist effizienter, als den gesamten Klassenpfad wie in meiner Antwort zu durchsuchen und ihn jedes Mal zu erstellen, wie in der Antwort von Instantsoup.

+0

Aber dann muss ich die Karte jedes Mal ändern, wenn ich einen neuen Befehl hinzufügen/löschen? kann ich das nicht dynamisch oder so? – Alfred

+0

Ja, das habe ich auch nachher bemerkt und es bearbeitet, bevor ich deinen Kommentar gesehen habe :) – BalusC

+0

@Alfred. Sie könnten versuchen, zu reflektieren, aber es ist kaum die beste Wahl. Gehen Sie mit der Karte, halten Sie es an einem sicheren Ort, wo es einfach ist, neue Befehle hinzuzufügen. – Tom

3

Eine Möglichkeit könnte sein, eine Schnittstelle zu haben ICommand, dass der Rahmenvertrag für einen Befehl, zB:

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

Und dann könnte man die Java verwenden SPI Mechanismus auf Ihre verschiedenen Implementierungen automatisch entdecken und registrieren sie in a Map<String,ICommand> und dann tun knownCommandsMap.get(input).execute(ctx) oder etwas Ähnliches.

Dies ermöglicht Ihnen praktisch, Ihren Dienst von Befehlsimplementierungen zu entkoppeln und diese Pluggable effektiv zu machen.

Die Registrierung einer Implementierungsklasse mit dem SPI erfolgt durch Hinzufügen einer Datei namens vollqualifizierter Name Ihrer ICommand-Klasse (wenn sie also im Paket dummy ist, wird die Datei innerhalb Ihres Klassenpfads META-INF/dummy.ICommand sein). ll laden und registrieren sie als:

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

dies ist nur in JDK 6 verfügbar –

+0

Können Sie den SPI-Teil etwas besser erklären? – Alfred

+0

@fuzzy lollipop: Eigentlich war es schon in JDK 5, aber in einem nicht-öffentlichen Paket (sun.misc ...). Es sollte zuerst Teil von JDK 5 sein. – Romain

3

Wie wäre es mit Schnittstellen, eine Fabrik, und ein wenig Reflexion? Sie müssen weiterhin Ausnahmen bei ungültigen Eingaben behandeln, aber Sie müssen dies immer tun. Mit dieser Methode fügen Sie einfach eine neue Implementierung von Executor für eine neue Eingabe hinzu.

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 
    } 
} 

Ihr Codebeispiel wird dann

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

Das ist auch eine nette Idee, es hat nur die Kosten für das Erstellen einer neuen Instanz jedes Mal. – BalusC

+0

Yup ich mag diese Idee auch. – Alfred

+1

Woher kommt der Name des Executors?Wenn es sich um eine Client-/Benutzereingabe handelt, kann ein böswilliger Benutzer jede Klasse auf dem System instanziieren. Wenn die Klasseninitialisierung den Status im System ändert, erhält der Angreifer eine gewisse Kontrolle über das System. Dann würde es eine Race-Bedingung zwischen der Instanziierung und der Exception geben, die geworfen wird (vorausgesetzt, die Klasse ist keine Unterklasse von Executor), während der der Angreifer alle Funktionen der anderen Klasse nutzen konnte ... – atk

2

Aufbau der Befehl patten auf einer Enum-Klasse können einige der Standardcode reduzieren. Nehmen wir an, dass x in input.equals(x) ist „XX“ und y in input.equals(y) ist „YY“

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

Auch eine nette Idee, aber das ist ziemlich eng gekoppelt. Sie können keine Befehle von "außerhalb" mehr bereitstellen. – BalusC

1

Sie sagen, dass Sie eine Eingabe von einem Sockel sind zu verarbeiten. Wie viel Input? Wie komplex ist das? Wie strukturiert ist das?

Je nach den Antworten auf diese Fragen können Sie eine Grammatik besser schreiben und einen Parsergenerator (z. B. ANTLR) den Eingabeverarbeitungscode generieren lassen.

+0

Nur ein einfaches Protokoll. Zum Beispiel memcached. – Alfred

+1

@Alfred - im Fall von Memcached, das ein sehr einfaches Protokoll ist, das ein halbes Dutzend Operationen hat und sich kaum ändern wird, würde ich ein If-Else-Konstrukt verwenden. Es gibt keinen Grund, Ihren Code-Modus komplex zu machen, nur um ihn zu "objektorientieren". Ich würde jedoch ein Objekt erstellen, um den Befehl und/oder die Antwort zu umbrechen und das gesamte Parsing zu handhaben. Und wahrscheinlich erstellen Sie eine enum, um den Befehl darzustellen (was die if-else-Kette in einen Schalter verwandeln würde). – Anon

+1

Wenn ich in der Lage sein würde, das Verhalten einfach zu ersetzen, könnte ich das Template Method-Muster verwenden, abstrakte Methoden für jede der Operationen erstellen und meine App Subklassen instanziieren. – Anon

Verwandte Themen