Daftar panjang pernyataan if di Java

101

Maaf, saya tidak dapat menemukan pertanyaan untuk menjawab ini, saya hampir yakin orang lain telah mengangkatnya sebelumnya.

Masalah saya adalah saya sedang menulis beberapa pustaka sistem untuk menjalankan perangkat yang disematkan. Saya memiliki perintah yang dapat dikirim ke perangkat ini melalui siaran radio. Ini hanya dapat dilakukan dengan teks. di dalam perpustakaan sistem saya memiliki utas yang menangani perintah yang terlihat seperti ini

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

Masalahnya adalah bahwa ada banyak perintah yang akan dengan cepat berubah menjadi sesuatu yang tidak terkendali. Mengerikan untuk diwaspadai, menyakitkan untuk di-debug, dan membingungkan untuk dipahami dalam waktu beberapa bulan.

Steve
sumber
16
Hanya komentar - Saya sangat menyarankan untuk mengambil buku Gang of Four patterns, atau jika Anda baru mengenal pola, buku Head First Design Patterns in Java (yang merupakan bacaan yang cukup mudah dan pengantar yang bagus untuk sejumlah pola umum ). Keduanya adalah sumber daya yang berharga, dan keduanya telah menghemat daging saya lebih dari sekali.
aperkins
2
Ya sebenarnya saya memilikinya tetapi mereka hilang :) Itulah mengapa saya yakin apa yang saya lakukan salah :) Namun tidak dapat menemukan solusi yang tepat! Mungkin ini mendapat posisi google yang bagus
Steve
2
Ini hanya Pola Perintah Senin di sini!
Nick Veys

Jawaban:

171

menggunakan pola Perintah :

public interface Command {
     void exec();
}

public class CommandA() implements Command {

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

// etc etc

lalu buat Map<String,Command>objek dan isi dengan Commandinstance:

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

maka Anda dapat mengganti rantai if / else if Anda dengan:

commandMap.get(value).exec();

EDIT

Anda juga dapat menambahkan perintah khusus seperti UnknownCommandatau NullCommand, tetapi Anda memerlukan perintah CommandMapyang menangani kasus sudut ini untuk meminimalkan pemeriksaan klien.

dfa
sumber
1
... dengan pemeriksaan yang sesuai bahwa commandMap.get () tidak mengembalikan null :-)
Brian Agnew
3
tentu saja, saya telah menghilangkan beberapa kode boilerplate demi kesederhanaan
dfa
10
Alih-alih HashMap Anda dapat menggunakan Java enum, yang memberi Anda seperangkat perintah yang ditentukan dengan baik, bukan peta yang lembek. Anda bisa memiliki getter di enum: Command getCommand (); atau bahkan mengimplementasikan exec () sebagai metode abstrak dalam enum, yang diimplementasikan oleh setiap instance (enum sebagai perintah).
JeeBee
2
ini akan memaksa untuk mengimplementasikan semua perintah di enum ... yang jauh ideal. Dengan antarmuka Anda juga dapat menerapkan pola Dekorator (misalnya DebugCommandDecorator, TraceCommandDecorator), ada lebih banyak fleksibilitas bawaan dalam antarmuka Java yang sederhana
dfa
5
Oke, untuk kumpulan perintah yang kecil dan tidak pernah berkembang, enum adalah solusi yang layak.
dfa
12

Saran saya akan menjadi semacam kombinasi ringan dari enum dan objek Command. Ini adalah idiom yang direkomendasikan oleh Joshua Bloch di Item 30 dari Java Efektif.

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

Tentu saja Anda bisa meneruskan parameter ke doCommand atau memiliki tipe kembalian.

Solusi ini mungkin tidak benar-benar cocok jika implementasi doCommand tidak benar-benar "sesuai" dengan jenis enum, yang - seperti biasa ketika Anda harus melakukan tradeoff - sedikit kabur.

jens
sumber
7

Memiliki enum perintah:

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

Jika Anda memiliki lebih dari beberapa perintah, perhatikan penggunaan pola Command, seperti yang dijawab di tempat lain (meskipun Anda dapat mempertahankan enum dan menyematkan panggilan ke kelas implementasi dalam enum, alih-alih menggunakan HashMap). Silakan lihat jawaban Andreas atau jens untuk pertanyaan ini sebagai contoh.

JeeBee
sumber
5
untuk setiap perintah baru yang Anda tambahkan, Anda perlu mengedit sakelar: kode ini tidak mengikuti prinsip buka / tutup
dfa
Tergantung apakah perintahnya sedikit, atau banyak, bukan? Juga situs ini sangat lambat akhir-akhir ini sehingga dibutuhkan 5 kali percobaan untuk mengedit jawaban.
JeeBee
ini tidak optimal, lihat stackoverflow.com/questions/1199646/… tentang cara melakukan ini dengan lebih optimal.
Andreas Petersson
Ya, terima kasih telah meluangkan waktu untuk mengimplementasikan apa yang saya tulis di bagian bawah komentar saya - Java Enum as Command Pattern. Jika saya bisa mengedit posting saya, saya akan menyebutkan ini, tetapi situs ini sedang sekarat.
JeeBee
Saya pikir pertanyaan ini menyerukan pernyataan Switch!
Michael Brown
7

Menerapkan antarmuka seperti yang didemonstrasikan secara sederhana dan jelas oleh dfa adalah bersih dan elegan (dan cara yang didukung "secara resmi"). Untuk inilah konsep antarmuka dimaksudkan.

Di C #, kita bisa menggunakan delegasi untuk programmer yang suka menggunakan penunjuk functon di c, tetapi teknik DFA adalah cara menggunakannya.

Anda juga bisa memiliki array

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

Kemudian Anda bisa menjalankan perintah dengan index

commands[7].exec();

Menjiplak dari DFA, tetapi memiliki kelas dasar abstrak, bukan antarmuka. Perhatikan cmdKey yang akan digunakan nanti. Berdasarkan pengalaman, saya menyadari bahwa sering kali sebuah perintah peralatan memiliki sub-perintah juga.

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

Bangun perintah Anda demikian,

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

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

Anda kemudian dapat memperluas HashMap atau HashTable generik dengan menyediakan fungsi penghisapan pasangan nilai kunci:

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

Kemudian buat penyimpanan perintah Anda:

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

Sekarang Anda dapat mengirim kontrol secara objektif

commands.get("A").exec();
commands.get(condition).exec();
Geek yang Terberkati
sumber
1 untuk menyebutkan delegasi untuk berjaga-jaga jika ada orang .NET yang melihat pertanyaan ini dan menjadi gila dengan antarmuka satu metode. Tapi mereka benar-benar tidak sebanding dengan pointer fungsi. Mereka lebih mirip dengan versi pola perintah yang didukung bahasa.
Daniel Earwicker
5

Saya menyarankan untuk membuat objek perintah dan memasukkannya ke dalam hashmap menggunakan String sebagai Kunci.

keuleJ
sumber
3

Bahkan jika saya yakin pendekatan pola perintah lebih mengarah pada praktik terbaik dan dapat dipelihara dalam jangka panjang, berikut adalah satu opsi liner untuk Anda:

org.apache.commons.beanutils.MethodUtils.invokeMethod (ini, "doCommand" + nilai, null);

svachon.dll
sumber
2

saya biasanya mencoba menyelesaikannya seperti itu:

public enum Command {

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

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

abstract void exec();
 }

ini memiliki banyak keuntungan:

1) tidak mungkin menambahkan enum tanpa mengimplementasikan exec. jadi Anda tidak akan melewatkan A.

2) Anda bahkan tidak perlu menambahkannya ke peta perintah apa pun, jadi tidak ada kode boilerplate untuk membuat peta. hanya metode abstrak dan implementasinya. (yang bisa dibilang juga boilerplate, tapi tidak akan menjadi lebih pendek ..)

3) Anda akan menyimpan siklus cpu yang terbuang dengan menelusuri daftar panjang if's atau menghitung kode hash dan melakukan pencarian.

edit: jika Anda tidak memiliki enum tetapi string sebagai sumber, cukup gunakan Command.valueOf(mystr).exec()untuk memanggil metode exec. perhatikan bahwa Anda harus menggunakan pengubah publik di execif yang ingin Anda panggil dari paket lain.

Andreas Petersson
sumber
2

Anda mungkin paling baik menggunakan Peta Perintah.

Tetapi apakah Anda memiliki satu set ini untuk ditangani, Anda berakhir dengan banyak Maps yang mengetuk. Maka itu layak untuk dilakukan dengan Enums.

Anda dapat melakukannya dengan Enum tanpa menggunakan sakelar (Anda mungkin tidak memerlukan getter dalam contoh), jika Anda menambahkan metode ke Enum untuk menyelesaikan "nilai". Kemudian Anda bisa melakukan:

Pembaruan: menambahkan peta statis untuk menghindari iterasi pada setiap panggilan. Dicubit tanpa malu dari jawaban ini .

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();
    }
}
Penjual Kaya
sumber
2

Jawaban yang diberikan oleh @dfa adalah solusi terbaik menurut saya.

Saya hanya memberikan beberapa cuplikan jika Anda menggunakan Java 8 dan ingin menggunakan Lambdas!

Perintah tanpa parameter:

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

(Anda bisa menggunakan Runnable alih-alih Command, tetapi saya tidak menganggapnya benar secara semantik):

Perintah dengan satu parameter:

Jika Anda mengharapkan parameter yang dapat Anda gunakan 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);

Dalam contoh di atas, doSomethingXadalah metode yang ada di myObjkelas yang mengambil Objek apa pun (dinamai paramdalam contoh ini) sebagai argumen.

Marlon Bernardes
sumber
1

jika Anda memiliki beberapa pernyataan 'jika' imbricated, maka ini adalah pola untuk menggunakan mesin aturan . Lihat, misalnya JBOSS Drools .

Pierre
sumber
0

jika memungkinkan untuk memiliki serangkaian prosedur (yang Anda sebut perintah) yang akan berguna ..

tetapi Anda dapat menulis program untuk menulis kode Anda. Semuanya sangat sistematis jika (value = 'A') commandA (); lain jika (........................ dll

pengguna147042
sumber
0

Saya tidak yakin apakah Anda memiliki tumpang tindih antara perilaku berbagai perintah Anda, tetapi Anda mungkin juga ingin melihat pola Chain Of Responsibility yang dapat memberikan lebih banyak fleksibilitas dengan mengizinkan banyak perintah untuk menangani beberapa nilai input.

tinyd
sumber
0

Pola perintah adalah cara untuk pergi. Berikut salah satu contoh menggunakan java 8:

1. Tentukan antarmuka:

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

2. Implementasikan antarmuka dengan setiap ekstensi:

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

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

dan

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

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

dan seterusnya .....

3. Tentukan Klien:

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. Dan ini adalah contoh hasil:

  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.dll
sumber
-1

Jika itu melakukan banyak hal, maka akan ada banyak kode, Anda tidak bisa benar-benar menjauh dari itu. Buatlah mudah untuk diikuti, beri nama variabel yang sangat berarti, komentar juga dapat membantu ...

Menandai
sumber