Longa lista de instruções if em Java

101

Desculpe, não consigo encontrar uma pergunta respondendo a esta, tenho quase certeza de que outra pessoa a levantou antes.

Meu problema é que estou escrevendo algumas bibliotecas do sistema para executar dispositivos incorporados. Tenho comandos que podem ser enviados a esses dispositivos por meio de transmissões de rádio. Isso só pode ser feito por texto. dentro das bibliotecas do sistema, eu tenho um thread que lida com os comandos que se parecem com este

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

O problema é que há muitos comandos para que ele rapidamente se torne algo fora de controle. Horrível de olhar, doloroso de depurar e incompreensível de entender em alguns meses.

Steve
fonte
16
Apenas um comentário - Eu recomendo fortemente pegar o livro Gang of Four patterns, ou se você for novo em padrões, o livro Head First Design Patterns em Java (que é uma leitura muito fácil e uma ótima introdução a uma série de padrões comuns ) Ambos são recursos valiosos e ambos salvaram meu bacon mais de uma vez.
aperkins de
2
Sim, na verdade eu os possuí, mas eles estão faltando :) É por isso que eu tinha certeza que o que estava fazendo era errado :) Porém, não consegui encontrar uma solução correta! Talvez isso consiga uma boa posição no Google
Steve
2
É só o padrão de comando segunda-feira aqui!
Nick Veys

Respostas:

171

usando o padrão de comando :

public interface Command {
     void exec();
}

public class CommandA() implements Command {

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

// etc etc

em seguida, crie um Map<String,Command>objeto e preencha-o com Commandinstâncias:

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

então você pode substituir sua corrente if / else if por:

commandMap.get(value).exec();

EDITAR

você também pode adicionar comandos especiais como UnknownCommandou NullCommand, mas precisa de um CommandMapque trate desses casos complicados para minimizar as verificações do cliente.

dfa
fonte
1
... com a verificação apropriada de que commandMap.get () não retorna null :-)
Brian Agnew
3
é claro, omiti algum código clichê para simplificar
dfa
10
Em vez de um HashMap, você pode usar um enum Java, que fornece um conjunto bem definido de comandos em vez de um mapa piegas. Você poderia ter um getter no enum: Command getCommand (); ou até mesmo implementar exec () como um método abstrato no enum, que cada instância implementa (enum como comando).
JeeBee
2
isso forçará a implementa todos os comandos no enum ... isso está longe de ser ideal. Com uma interface, você também pode aplicar o padrão Decorator (por exemplo, DebugCommandDecorator, TraceCommandDecorator), há muito mais flexibilidade integrada em uma interface Java simples
dfa
5
Ok, para um conjunto de comandos pequeno e nunca crescente, um enum é uma solução viável.
dfa
12

Minha sugestão seria uma espécie de combinação leve de enum e objeto Command. Este é um idioma recomendado por Joshua Bloch no item 30 de 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();
}

Claro, você pode passar parâmetros para doCommand ou ter tipos de retorno.

Esta solução pode não ser realmente adequada se as implementações de doCommand não se "ajustarem" realmente ao tipo enum, que é - como de costume quando você tem que fazer uma troca - um pouco confuso.

jens
fonte
7

Tenha um enum de comandos:

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 você tiver mais do que alguns comandos, procure usar o padrão Command, conforme respondido em outro lugar (embora você possa manter o enum e incorporar a chamada à classe de implementação dentro do enum, em vez de usar um HashMap). Por favor, veja a resposta de Andreas ou jens a esta pergunta para um exemplo.

JeeBee
fonte
5
para cada novo comando adicionado, você precisa editar a opção: este código não segue o princípio de abrir / fechar
dfa
Depende se os comandos são poucos ou muitos, não é? Além disso, este site está tão terrivelmente lento hoje em dia que leva 5 tentativas para editar uma resposta.
JeeBee
isso não é ideal, consulte stackoverflow.com/questions/1199646/… para saber como fazer isso da melhor maneira.
Andreas Petersson
Sim, obrigado por gastar seu tempo para implementar o que escrevi no final do meu comentário - Java Enum as Command Pattern. Se eu pudesse editar meu post, mencionaria isso, mas este site está morrendo.
JeeBee
Eu acho que esta questão está clamando por uma declaração Switch!
Michael Brown
7

Implementar uma interface conforme demonstrado de forma simples e clara pelo dfa é limpo e elegante (e uma forma com suporte "oficial"). É para isso que se destina o conceito de interface.

Em C #, poderíamos usar delegados para programadores que gostam de usar ponteiros de função em c, mas a técnica do DFA é a maneira de usar.

Você também poderia ter um array

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

Então você pode executar um comando por índice

commands[7].exec();

Plagiando do DFA, mas tendo uma classe base abstrata em vez de uma interface. Observe o cmdKey que seria usado posteriormente. Por experiência, percebo que frequentemente um comando de equipamento também possui subcomandos.

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

Construa seus comandos assim,

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

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

Você poderia então estender HashMap ou HashTable genérico fornecendo uma função de sucção de par de valores-chave:

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

Em seguida, construa seu armazenamento de comando:

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

Agora você pode enviar controles objetivamente

commands.get("A").exec();
commands.get(condition).exec();
Abençoado Geek
fonte
+1 para mencionar delegados apenas no caso de qualquer pessoa .NET ver esta questão e enlouquecer com as interfaces de um método. Mas eles realmente não são comparáveis ​​aos ponteiros de função. Eles estão mais próximos de uma versão do padrão de comando com suporte por idioma.
Daniel Earwicker,
5

Bem, eu sugiro criar objetos de comando e colocá-los em um hashmap usando a String como chave.

keuleJ
fonte
3

Mesmo que eu acredite que a abordagem do padrão de comando é mais voltada para as melhores práticas e manutenção a longo prazo, aqui está uma opção de linha única para você:

org.apache.commons.beanutils.MethodUtils.invokeMethod (this, "doCommand" + valor, nulo);

Svachon
fonte
2

Eu geralmente tento resolver dessa forma:

public enum Command {

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

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

abstract void exec();
 }

isso tem muitas vantagens:

1) não é possível adicionar um enum sem implementar exec. então você não vai perder um A.

2) você nem mesmo terá que adicioná-lo a nenhum mapa de comando, portanto, nenhum código clichê para construir o mapa. apenas o método abstrato e suas implementações. (que é indiscutivelmente também clichê, mas não ficará mais curto ..)

3) você salvará todos os ciclos de CPU desperdiçados, passando por uma longa lista de if's ou calculando hashCodes e fazendo pesquisas.

edit: se você não tem enums, mas strings como fonte, use Command.valueOf(mystr).exec()para chamar o método exec. observe que você deve usar o modificador público em exec se quiser chamá-lo de outro pacote.

Andreas Petersson
fonte
2

Você provavelmente está melhor usando um Mapa de Comandos.

Mas se você tiver um conjunto desses para lidar com você acaba com um monte de mapas batendo por aí. Então, vale a pena tentar fazer isso com Enums.

Você pode fazer isso com um Enum sem usar opções (provavelmente não precisa dos getters no exemplo), se adicionar um método ao Enum para resolver o "valor". Então você pode apenas fazer:

Update: adicionado mapa estático para evitar iteração em cada chamada. Desavergonhadamente tirado dessa resposta .

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();
    }
}
Vendedor rico
fonte
2

A resposta fornecida por @dfa é a melhor solução, na minha opinião.

Estou apenas fornecendo alguns trechos , caso você esteja usando Java 8 e queira usar Lambdas!

Comando sem parâmetros:

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();

(você poderia usar um Runnable em vez de Command, mas não o considero semanticamente correto):

Comando com um parâmetro:

Caso você espere um parâmetro, pode usar 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);

No exemplo acima, doSomethingXé um método presente na myObjclasse de que recebe qualquer Objeto (nomeado paramneste exemplo) como um argumento.

Marlon Bernardes
fonte
1

se você tiver várias instruções 'if' imbricadas, esse é um padrão para usar um mecanismo de regras . Veja, por exemplo, JBOSS Drools .

Pierre
fonte
0

se fosse possível ter uma série de procedimentos (o que você chamou de comandos) que seria útil.

mas você pode escrever um programa para escrever seu código. É tudo muito sistemático if (value = 'A') commandA (); else if (........................ etc

user147042
fonte
0

Não tenho certeza se há alguma sobreposição entre o comportamento de seus vários comandos, mas você também pode dar uma olhada no padrão Chain Of Responsibility, que pode fornecer mais flexibilidade, permitindo que vários comandos manipulem alguns valores de entrada.

tinyd
fonte
0

O padrão de comando é o caminho a seguir. Aqui está um exemplo usando java 8:

1. Defina a interface:

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

2. Implemente a interface com cada uma das extensões:

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 assim por diante .....

3. Defina o 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 este é o resultado da amostra:

  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"
  }
nxhoaf
fonte
-1

Se ele faz muitas coisas, então haverá muito código, você não pode realmente fugir disso. Apenas torne mais fácil seguir, dê às variáveis ​​nomes muito significativos, comentários também podem ajudar ...

Marca
fonte