Lungo elenco di istruzioni if ​​in Java


101

Mi dispiace non riesco a trovare una domanda che risponda, sono quasi certo che qualcun altro l'abbia già sollevata.

Il mio problema è che sto scrivendo alcune librerie di sistema per eseguire dispositivi incorporati. Ho comandi che possono essere inviati a questi dispositivi tramite trasmissioni radio. Questo può essere fatto solo tramite testo. all'interno delle librerie di sistema ho un thread che gestisce i comandi che assomiglia a questo

if (value.equals("A")) { doCommandA() }
else if (value.equals("B")) { doCommandB() } 
else if etc. 

Il problema è che ci sono molti comandi che porteranno rapidamente a qualcosa fuori controllo. Orribile da guardare, doloroso da mettere a punto e sbalorditivo da capire in pochi mesi.


16
Solo un commento: consiglio vivamente di prendere in mano il libro Gang of Four patterns, o se sei nuovo ai patterns, il libro Head First Design Patterns in Java (che è una lettura abbastanza facile e un'ottima introduzione a una serie di modelli comuni ). Entrambe sono risorse preziose ed entrambe hanno salvato la mia pancetta più di una volta.
aperkins

2
Sì, in realtà li possedevo ma mancano :) Ecco perché ero sicuro che quello che stavo facendo fosse sbagliato :) Non sono riuscito a trovare una soluzione corretta! Forse questo ottiene una bella posizione su Google
Steve il

2
È solo Command Pattern Monday qui!
Nick Veys

Risposte:


171

utilizzando il modello di comando :

public interface Command {
     void exec();
}

public class CommandA() implements Command {

     void exec() {
          // ... 
     }
}

// etc etc

quindi crea un Map<String,Command>oggetto e popolalo con Commandistanze:

commandMap.put("A", new CommandA());
commandMap.put("B", new CommandB());

allora puoi sostituire la tua catena if / else if con:

commandMap.get(value).exec();

MODIFICARE

puoi anche aggiungere comandi speciali come UnknownCommando NullCommand, ma è necessario CommandMapche gestisca questi casi d'angolo per ridurre al minimo i controlli del cliente.


1
... con l'apposito controllo che commandMap.get () non restituisca null :-)
Brian Agnew

3
ovviamente, ho omesso del codice boilerplate per semplicità
dfa

10
Invece di una HashMap puoi usare un'enumerazione Java, che ti dà un insieme ben definito di comandi invece di una mappa pastosa. Potresti avere un getter nell'enumerazione: Command getCommand (); o anche implementare exec () come metodo astratto nell'enum, che ogni istanza implementa (enum as command).
JeeBee

2
questo costringerà a implementare tutti i comandi nell'enum ... che è tutt'altro che ideale. Con un'interfaccia puoi applicare anche il pattern Decorator (es. DebugCommandDecorator, TraceCommandDecorator), c'è molta più flessibilità incorporata in una semplice interfaccia Java
dfa

5
Ok, per un insieme di comandi piccolo e mai in crescita un enum è una soluzione praticabile.
dfa

12

Il mio suggerimento sarebbe una sorta di combinazione leggera di enum e oggetto Command. Questo è un idioma consigliato da Joshua Bloch nell'articolo 30 di Effective Java.

public enum Command{
  A{public void doCommand(){
      // Implementation for A
    }
  },
  B{public void doCommand(){
      // Implementation for B
    }
  },
  C{public void doCommand(){
      // Implementation for C
    }
  };
  public abstract void doCommand();
}

Ovviamente potresti passare parametri a doCommand o avere tipi di ritorno.

Questa soluzione potrebbe non essere realmente adatta se le implementazioni di doCommand non si "adattano" al tipo enum, che è - come al solito quando devi fare un compromesso - un po 'confuso.


7

Avere un enum di comandi:

public enum Commands { A, B, C; }
...

Command command = Commands.valueOf(value);

switch (command) {
    case A: doCommandA(); break;
    case B: doCommandB(); break;
    case C: doCommandC(); break;
}

Se hai più di pochi comandi, cerca di utilizzare il modello di comando, come risposto altrove (anche se puoi mantenere l'enum e incorporare la chiamata alla classe di implementazione all'interno dell'enum, invece di usare una HashMap). Si prega di vedere la risposta di Andreas o Jens a questa domanda per un esempio.


5
per ogni nuovo comando che aggiungi, devi modificare lo switch: questo codice non segue il principio di apertura / chiusura
dfa

Dipende dal fatto che i comandi siano pochi o tanti, no? Anche questo sito è così spaventosamente lento in questi giorni che ci vogliono 5 tentativi per modificare una risposta.
JeeBee

questo non è ottimale vedi stackoverflow.com/questions/1199646/… su come farlo in modo più ottimale.
Andreas Petersson,

Sì, grazie per aver speso del tempo per implementare ciò che ho scritto in fondo al mio commento: Java Enum come schema di comando. Se potessi modificare il mio post, lo menzionerei, ma questo sito sta morendo.
JeeBee

Penso che questa domanda stia urlando per una dichiarazione di Switch!
Michael Brown,

7

L'implementazione di un'interfaccia come dimostrata in modo semplice e chiaro da dfa è pulita ed elegante (e supportata "ufficialmente"). Questo è ciò per cui si intende il concetto di interfaccia.

In C #, potremmo usare i delegati per i programmatori a cui piace usare i puntatori a functon in c, ma la tecnica di DFA è il modo di usare.

Potresti avere anche un array

Command[] commands =
{
  new CommandA(), new CommandB(), new CommandC(), ...
}

Quindi potresti eseguire un comando tramite index

commands[7].exec();

Plagio da DFA, ma con una classe di base astratta invece di un'interfaccia. Notare il cmdKey che verrà utilizzato in seguito. Per esperienza, mi rendo conto che spesso un comando di apparecchiatura ha anche dei sottocomandi.

abstract public class Command()
{
  abstract public byte exec(String subCmd);
  public String cmdKey;
  public String subCmd;
}

Costruisci così i tuoi comandi,

public class CommandA
extends Command
{
  public CommandA(String subCmd)
  {
    this.cmdKey = "A";
    this.subCmd = subCmd;
  }

  public byte exec()
  {
    sendWhatever(...);
    byte status = receiveWhatever(...);
    return status;
  }
}

È quindi possibile estendere HashMap o HashTable generici fornendo una funzione di succhiamento coppia chiave-valore:

public class CommandHash<String, Command>
extends HashMap<String, Command>
(
  public CommandHash<String, Command>(Command[] commands)
  {
    this.commandSucker(Command[] commands);
  }
  public commandSucker(Command[] commands)
  {
    for(Command cmd : commands)
    {
      this.put(cmd.cmdKey, cmd);
    }
  }
}

Quindi costruisci il tuo archivio di comandi:

CommandHash commands =
  new CommandHash(
  {
    new CommandA("asdf"),
    new CommandA("qwerty"),
    new CommandB(null),
    new CommandC("hello dolly"),
    ...
  });

Ora puoi inviare i controlli in modo obiettivo

commands.get("A").exec();
commands.get(condition).exec();

+1 per aver menzionato i delegati nel caso in cui qualsiasi persona .NET veda questa domanda e impazzisca con le interfacce a un metodo. Ma in realtà non sono paragonabili ai puntatori a funzione. Sono più vicini a una versione supportata dalla lingua del modello di comando.
Daniel Earwicker

5

Bene, suggerisco di creare oggetti comando e inserirli in una hashmap usando la stringa come chiave.


3

Anche se credo che l'approccio del modello di comando sia più orientato verso le migliori pratiche e mantenibile a lungo termine, ecco un'opzione di una linea per te:

org.apache.commons.beanutils.MethodUtils.invokeMethod (questo, "docommand" + valore nullo);


2

Di solito cerco di risolverlo in questo modo:

public enum Command {

A {void exec() {
     doCommandA();
}},

B {void exec() {
    doCommandB();
}};

abstract void exec();
 }

questo ha molti vantaggi:

1) non è possibile aggiungere un enum senza implementare exec. così non ti perderai una A.

2) non dovrai nemmeno aggiungerlo a nessuna mappa dei comandi, quindi nessun codice boilerplate per costruire la mappa. solo il metodo astratto e le sue implementazioni. (che è probabilmente anche standard, ma non sarà più breve ..)

3) salverai tutti i cicli sprecati della CPU passando attraverso un lungo elenco di if o calcolando hashCodes ed eseguendo ricerche.

modifica: se non hai enumerazioni ma stringhe come sorgente, usa Command.valueOf(mystr).exec()per chiamare il metodo exec. nota che devi usare il modificatore public su exec se vuoi chiamarlo da un altro pacchetto.


2

Probabilmente è meglio usare una mappa dei comandi.

Ma se ne hai una serie da gestire, ti ritroverai con un sacco di mappe che bussano. Allora vale la pena guardare a farlo con Enums.

Puoi farlo con un Enum senza usare le opzioni (probabilmente non hai bisogno dei getter nell'esempio), se aggiungi un metodo a Enum per risolvere per "valore". Quindi puoi semplicemente fare:

Aggiornamento: aggiunta mappa statica per evitare iterazioni su ogni chiamata. Spudoratamente pizzicato da questa risposta .

Commands.getCommand(value).exec();

public interface Command {
    void exec();
}

public enum Commands {
    A("foo", new Command(){public void exec(){
        System.out.println(A.getValue());
    }}),
    B("bar", new Command(){public void exec(){
        System.out.println(B.getValue());
    }}),
    C("barry", new Command(){public void exec(){
        System.out.println(C.getValue());
    }});

    private String value;
    private Command command;
    private static Map<String, Commands> commandsMap;

    static {
        commandsMap = new HashMap<String, Commands>();
        for (Commands c : Commands.values()) {
            commandsMap.put(c.getValue(), c);    
        }
    }

    Commands(String value, Command command) {
        this.value= value;
        this.command = command;
    }

    public String getValue() {
        return value;
    }

    public Command getCommand() {
        return command;
    }

    public static Command getCommand(String value) {
        if(!commandsMap.containsKey(value)) {
            throw new RuntimeException("value not found:" + value);
        }
        return commandsMap.get(value).getCommand();
    }
}

2

La risposta fornita da @dfa è la soluzione migliore, secondo me.

Sto solo fornendo alcuni snippet nel caso in cui utilizzi Java 8 e desideri utilizzare Lambdas!

Comando senza parametri:

Map<String, Command> commands = new HashMap<String, Command>();
commands.put("A", () -> System.out.println("COMMAND A"));
commands.put("B", () -> System.out.println("COMMAND B"));
commands.put("C", () -> System.out.println("COMMAND C"));
commands.get(value).exec();

(potresti usare un Runnable invece di Command, ma non lo considero semanticamente corretto):

Comando con un parametro:

Nel caso in cui ti aspetti un parametro potresti usare java.util.function.Consumer:

Map<String, Consumer<Object>> commands = new HashMap<String, Consumer<Object>>();
commands.put("A", myObj::doSomethingA);
commands.put("B", myObj::doSomethingB);
commands.put("C", myObj::doSomethingC);
commands.get(value).accept(param);

Nell'esempio sopra, doSomethingXè un metodo presente nella myObjclasse di che accetta qualsiasi Object (denominato paramin questo esempio) come argomento.


1

se sono presenti più istruzioni "if" incorporate, questo è un modello per l'utilizzo di un motore di regole . Vedi, ad esempio, JBOSS Drools .



0

se fosse possibile avere un array di procedure (ciò che hai chiamato comandi) sarebbe utile ..

ma potresti scrivere un programma per scrivere il tuo codice. È tutto molto sistematico if (value = 'A') commandA (); altrimenti se (........................ ecc


0

Non sono sicuro che tu abbia qualche sovrapposizione tra il comportamento dei tuoi vari comandi, ma potresti anche voler dare un'occhiata al pattern Chain Of Responsibility che potrebbe fornire maggiore flessibilità consentendo a più comandi di gestire alcuni valori di input.


0

Il modello di comando è la strada da percorrere. Ecco un esempio utilizzando java 8:

1. Definisci l'interfaccia:

public interface ExtensionHandler {
  boolean isMatched(String fileName);
  String handle(String fileName);
}

2. Implementa l'interfaccia con ciascuna estensione:

public class PdfHandler implements ExtensionHandler {
  @Override
  public boolean isMatched(String fileName) {
    return fileName.endsWith(".pdf");
  }

  @Override
  public String handle(String fileName) {
    return "application/pdf";
  }
}

e

public class TxtHandler implements ExtensionHandler {
  @Override public boolean isMatched(String fileName) {
    return fileName.endsWith(".txt");
  }

  @Override public String handle(String fileName) {
    return "txt/plain";
  }
}

e così via .....

3. Definisci il cliente:

public class MimeTypeGetter {
  private List<ExtensionHandler> extensionHandlers;
  private ExtensionHandler plainTextHandler;

  public MimeTypeGetter() {
    extensionHandlers = new ArrayList<>();

    extensionHandlers.add(new PdfHandler());
    extensionHandlers.add(new DocHandler());
    extensionHandlers.add(new XlsHandler());

    // and so on

    plainTextHandler = new PlainTextHandler();
    extensionHandlers.add(plainTextHandler);
  }

  public String getMimeType(String fileExtension) {
    return extensionHandlers.stream()
      .filter(handler -> handler.isMatched(fileExtension))
      .findFirst()
      .orElse(plainTextHandler)
      .handle(fileExtension);
  }
}

4. E questo è il risultato di esempio:

  public static void main(String[] args) {
    MimeTypeGetter mimeTypeGetter = new MimeTypeGetter();

    System.out.println(mimeTypeGetter.getMimeType("test.pdf")); // application/pdf
    System.out.println(mimeTypeGetter.getMimeType("hello.txt")); // txt/plain
    System.out.println(mimeTypeGetter.getMimeType("my presentation.ppt")); // "application/vnd.ms-powerpoint"
  }

-1

Se fa molte cose, ci sarà molto codice, non puoi davvero farne a meno. Rendilo facile da seguire, dai nomi molto significativi alle variabili, anche i commenti possono aiutare ...

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.