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


101

申し訳ありませんが、これに答える質問は見つかりません。他の誰かが以前に質問したことはほぼ間違いありません。

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

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

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


16
ただのコメント-Gang of Fourパターンの本、またはパターンに慣れていない場合は、JavaのHead First Design Patternsを入手することを強くお勧めします(これはかなり読みやすく、多くの一般的なパターンの優れた紹介です) )。どちらも貴重なリソースであり、どちらも私のベーコンを2回以上節約しました。
アパーキンス2009

2
はい、私は実際にそれらを所有していましたが、それらはありません:)それが私がやっていることが間違っていたと確信した理由です:)しかし、正しい解決策を見つけることができませんでした!多分これは素晴らしいグーグルポジションを得ます
スティーブ

2
ここは月曜日のコマンドパターンです。
Nick Veys、

回答:


171

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

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

編集する

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


1
... commandMap.get()がnullを返さないことを適切にチェックします:-)
ブライアンアグニュー

3
もちろん、私は、簡略化のためのいくつかの定型的なコードを省略しました
DFA

10
HashMapの代わりに、Java enumを使用できます。これにより、ぼやけたマップの代わりに、明確に定義されたコマンドのセットが提供されます。あなたは列挙型にゲッターを持つことができます:Command getCommand(); または、各インスタンスが実装する列挙型の抽象メソッドとしてexec()を実装します(コマンドとしての列挙型)。
JeeBee 2009

2
これにより、列挙型のすべてのコマンドが強制的に実装されます。あなたはまた、Decoratorパターンを(例えばDebugCommandDecorator、TraceCommandDecorator)適用可能なインタフェースを使用すると、内蔵のシンプルなJavaインターフェイスで、より多くの柔軟性がある
DFA

5
さて、小さくて決して増えないコマンドのセットの場合、列挙型は実行可能なソリューションです。
dfa 2009

12

私の提案は、列挙型とコマンドオブジェクトの一種の軽量な組み合わせです。これは、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の実装が列挙型に実際に「適合」しない場合、このソリューションは実際には適切ではない可能性があります。これは、通常、トレードオフを行う必要がある場合、少しあいまいです。


7

コマンドの列挙を持っている:

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を使用する代わりに、列挙型を保持し、実装クラスへの呼び出しを埋め込むことができます)。例として、この質問に対するAndreasまたはjensの回答を参照してください。


5
追加する新しいコマンドごとに、スイッチを編集する必要があります。このコードは開閉の原則に従っていません
dfa

コマンドが少ないか多いかによりますね。また、このサイトは最近恐ろしいほど遅く、回答の編集に5回の試行が必要です。
JeeBee 2009

これは最適ではありません。これをより最適にする方法については、stackoverflow.com / questions / 1199646 /…を参照してください。
Andreas Petersson

はい、コメントの最後に書いたもの、つまりコマンドパターンとしてのJava Enumの実装に時間を割いていただきありがとうございます。私の投稿を編集できたら、これについて言及しますが、このサイトは死にかけています。
JeeBee 2009

私はこの質問がSwitchステートメントに向かって叫んでいると思います!
マイケルブラウン

7

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

.NETの人々がこの質問を見て、1つのメソッドのインターフェースに夢中になった場合に備えて、デリゲートに言及するための+1。しかし、それらは実際には関数ポインターに匹敵しません。これらは、コマンドパターンの言語サポートバージョンに近いものです。
Daniel Earwicker 2010年

5

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


3

コマンドパターンアプローチの方がベストプラティックスに向かっており、長期的には保守可能であると私が信じている場合でも、ここに1つのライナーオプションがあります。

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


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

コマンドのマップを使用することをお勧めします。

しかし、あなたが処理するためのこれらのセットがあり、大量のマップがノックアウトすることになります。次に、列挙型でそれを行うことを検討する価値があります。

「値」を解決するメソッドを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

私の意見では、@ 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();

(コマンドの代わりにランナブルを使用することもできますが、意味的には適切とは見なしません):

パラメーターが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);

上記の例でdoSomethingXは、はmyObjクラスに存在するメソッドで、任意のオブジェクト(paramこの例では名前が付けられています)を引数として取ります。


1

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



0

便利な一連のプロシージャ(コマンドと呼ばれるもの)を用意することができた場合。

しかし、あなたは自分のコードを書くプログラムを書くことができます。if(value = 'A')commandA();はすべて非常に体系的です。else if(........................など


0

さまざまなコマンドの動作に重複があるかどうかはわかりませんが、複数のコマンドで一部の入力値を処理できるようにすることで、柔軟性を高めることができる責任連鎖パターンを確認することもできます。


0

コマンドパターンは進むべき道です。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";
  }
}

等々 .....

3.クライアントを定義します。

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

-1

それが多くのことをするなら、それからたくさんのコードがあるでしょう、あなたは本当にそれから逃れることができません。わかりやすくするだけで、変数にわかりやすい名前を付けてください。コメントも役立ちます...

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.