web-dev-qa-db-ja.com

Javaのifステートメントの長いリスト

申し訳ありませんが、これに答える質問を見つけることができません。他の誰かが以前にそれを提起したことはほぼ確実です。

私の問題は、組み込みデバイスを実行するためのシステムライブラリをいくつか作成していることです。これらのデバイスにラジオ放送で送信できるコマンドがあります。これはテキストでのみ実行できます。システムライブラリ内には、このようなコマンドを処理するスレッドがあります

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

問題は、多くのコマンドがあり、制御不能なものにすぐにスパイラルすることです。外見するのは恐ろしく、デバッグするのは苦痛で、数か月後には理解するのが面倒です。

98
Steve

コマンドパターン を使用:

public interface Command {
     void exec();
}

public class CommandA() implements Command {

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

// etc etc

その後、Map<String,Command>オブジェクトにCommandインスタンスを追加します。

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

次に、if/else ifチェーンを次のように置き換えることができます:

commandMap.get(value).exec();

[〜#〜] edit [〜#〜]

UnknownCommandNullCommandなどの特別なコマンドを追加することもできますが、クライアントのチェックを最小限に抑えるためにこれらのコーナーケースを処理するCommandMapが必要です。

167
dfa

私の提案は、列挙型オブジェクトとコマンドオブジェクトの一種の軽量な組み合わせです。これは、Effective JavaのItem 30でJoshua Blochが推奨するイディオムです。

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

もちろん、doCommandにパラメーターを渡すか、戻り値の型を使用できます。

DoCommandの実装が列挙型に実際に「適合」しない場合、この解決策は実際には適切ではない可能性があります。

12
jens

コマンドの列挙があります:

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

いくつかのコマンドがある場合は、他の場所で回答されているように、コマンドパターンの使用を検討してください(ただし、HashMapを使用する代わりに、enumを保持し、enum内の実装クラスへの呼び出しを埋め込むことができます)。例については、この質問に対するAndreasまたはjensの回答を参照してください。

7
JeeBee

Dfaによって簡単かつ明白に示されているように、インターフェイスを実装することは簡潔でエレガントです(そして「公式に」サポートされている方法です)。これは、インターフェースの概念が意味するものです。

C#では、cで関数ポインターを使用したいプログラマーにデリゲートを使用できますが、DFAの手法が使用方法です。

あなたも配列を持つことができます

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

次に、インデックスによってコマンドを実行できます

commands[7].exec();

DFAからの盗用ですが、インターフェースの代わりに抽象基本クラスがあります。後で使用されるcmdKeyに注意してください。経験から、機器コマンドにはサブコマンドもあることがよくあります。

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

このようにコマンドを作成し、

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

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

次に、キーと値のペアの吸引機能を提供することで、汎用のHashMapまたはHashTableを拡張できます。

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

次に、コマンドストアを構築します。

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

コントロールを客観的に送信できるようになりました

commands.get("A").exec();
commands.get(condition).exec();
7
Blessed Geek

さて、コマンドオブジェクトを作成し、文字列をキーとして使用してハッシュマップに入れることをお勧めします。

5
keuleJ

コマンドパターンアプローチが最高の実践に向けられており、長期的に維持可能であると私が信じているとしても、ここに1つのライナーオプションがあります。

org.Apache.commons.beanutils.MethodUtils.invokeMethod(this、 "doCommand" + value、null);

3
svachon

おそらく、コマンドのマップを使用するのが最善です。

しかし、これらのセットを使用して、大量のマップがぶらぶらすることになりますか?それから、Enumsでそれを行う価値があります。

「値」を解決するメソッドをEnumに追加する場合、スイッチを使用せずにEnumでそれを行うことができます(この例ではゲッターはおそらく必要ありません)。その後、あなたはちょうどすることができます:

更新:呼び出しごとの反復を避けるために静的マップを追加しました。 この回答 から恥知らずにつまむ。

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
Rich Seller

私の意見では、@ dfaが提供する答えが最善の解決策です。

私はいくつかのスニペットを提供していますJava 8を使用していて、ラムダを使用したい場合!

パラメーターなしのコマンド:

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

(コマンドの代わりにRunnableを使用することもできますが、意味的に正しいとは思いません):

1つのパラメーターを持つコマンド:

パラメータが必要な場合は、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);

上記の例では、doSomethingXmyObjのクラスに存在するメソッドであり、任意のオブジェクト(この例ではparam)を引数として取ります。

2

私は通常、そのように解決しようとします:

_public enum Command {

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

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

abstract void exec();
 }
_

これには多くの利点があります。

1)execを実装せずに列挙型を追加することはできません。 Aを見逃すことはありません.

2)コマンドマップに追加する必要がないため、マップを構築するための定型コードはありません。抽象メソッドとその実装のみ。 (おそらく定型でもありますが、短くなることはありません。)

3)ifの長いリストを調べるか、hashCodeを計算してルックアップを行うことにより、CPUサイクルの無駄を省きます。

編集:列挙型ではなく文字列をソースとして使用する場合は、Command.valueOf(mystr).exec()を使用してexecメソッドを呼び出します。別のパッケージから呼び出す場合は、execでpublic修飾子を使用する必要があることに注意してください。

2

複数の 'if'ステートメントが混在している場合、これはルールエンジンを使用するパターンです。たとえば、 JBOSS Drools を参照してください。

1
Pierre

ここで説明するように、HashMapを使用するだけです。

0
ars

便利なプロシージャ(コマンドと呼ばれるもの)の配列を作成できる場合.

ただし、コードを記述するプログラムを作成することはできます。すべて非常に体系的なif(value = 'A')commandA(); else if(........................ e.t.c.

0
user147042

さまざまなコマンドの動作に重複があるかどうかはわかりませんが、複数のコマンドを許可することで柔軟性を高めることができる Chain Of Responsibility パターンを確認することもできますいくつかの入力値を処理します。

0
tinyd

コマンドパターンがその方法です。 Java 8を使用した1つの例です。

1。インターフェイスを定義します:

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

2。各拡張機能でインターフェイスを実装します:

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

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

そして

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

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

等々 .....

。クライアントの定義:

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。これはサンプルの結果です:

  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"
  }
0
nxhoaf