รายการคำสั่ง if ใน Java แบบยาว


101

ขออภัยฉันไม่พบคำถามที่ตอบคำถามนี้ฉันเกือบจะแน่ใจว่ามีคนอื่นถามมาก่อน

ปัญหาของฉันคือฉันกำลังเขียนไลบรารีระบบเพื่อเรียกใช้อุปกรณ์ฝังตัว ฉันมีคำสั่งที่สามารถส่งไปยังอุปกรณ์เหล่านี้ผ่านการออกอากาศทางวิทยุ สามารถทำได้โดยใช้ข้อความเท่านั้น ภายในไลบรารีระบบฉันมีเธรดที่จัดการคำสั่งที่มีลักษณะเช่นนี้

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

ปัญหาคือมีคำสั่งมากมายที่จะทำให้บางสิ่งบางอย่างควบคุมไม่ได้อย่างรวดเร็ว น่ากลัวที่จะมองออกเจ็บปวดกับการแก้ไขจุดบกพร่องและไม่สามารถเข้าใจได้ในเวลาไม่กี่เดือน


16
เพียงแค่แสดงความคิดเห็น - ฉันขอแนะนำอย่างยิ่งให้หยิบหนังสือรูปแบบ Gang of Four หรือถ้าคุณยังใหม่กับรูปแบบหนังสือ Head First Design Patterns ใน Java (ซึ่งอ่านง่ายพอสมควรและเป็นคำแนะนำที่ดีเกี่ยวกับรูปแบบทั่วไป ). ทั้งสองอย่างเป็นทรัพยากรที่มีค่าและทั้งสองอย่างช่วยชีวิตฉันได้มากกว่าหนึ่งครั้ง
aperkins

2
ใช่จริงๆแล้วฉันเป็นเจ้าของ แต่มันหายไป :) นั่นคือเหตุผลที่ฉันแน่ใจว่าสิ่งที่ฉันทำนั้นผิด :) ไม่สามารถหาวิธีแก้ไขที่ถูกต้องได้! บางทีนี่อาจจะได้ตำแหน่ง Google ที่ดี
สตีฟ

2
เป็นเพียง Command Pattern Monday ที่นี่!
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 chain ด้วย:

commandMap.get(value).exec();

แก้ไข

คุณยังสามารถเพิ่มคำสั่งพิเศษเช่นUnknownCommandหรือNullCommandแต่คุณต้องมีคำสั่งCommandMapที่จัดการกรณีมุมเหล่านี้เพื่อลดการตรวจสอบของลูกค้า


1
... ด้วยการตรวจสอบที่เหมาะสมว่า commandMap.get () ไม่คืนค่า null :-)
Brian Agnew

3
แน่นอนฉันได้ละเว้นรหัสสำเร็จรูปเพื่อความเรียบง่าย
dfa

10
แทนที่จะใช้ HashMap คุณสามารถใช้ Java enum ซึ่งให้ชุดคำสั่งที่กำหนดไว้อย่างดีแทนการใช้แผนที่ที่ไม่สมบูรณ์ คุณสามารถมี getter ใน enum: Command getCommand (); หรือแม้กระทั่งใช้ exec () เป็นวิธีนามธรรมใน enum ซึ่งแต่ละอินสแตนซ์ใช้ (enum เป็นคำสั่ง)
JeeBee

2
สิ่งนี้จะบังคับให้ใช้คำสั่งทั้งหมดใน enum ... ด้วยอินเทอร์เฟซคุณสามารถใช้รูปแบบ Decorator ได้ (เช่น DebugCommandDecorator, TraceCommandDecorator) มีความยืดหยุ่นในตัวมากขึ้นในอินเทอร์เฟซ Java แบบธรรมดา
dfa

5
โอเคสำหรับชุดคำสั่งขนาดเล็กและไม่เติบโต enum เป็นวิธีแก้ปัญหาที่ทำงานได้
dfa

12

ข้อเสนอแนะของฉันคือการผสมผสานระหว่าง enum และ Command object ที่มีน้ำหนักเบา นี่เป็นสำนวนที่ Joshua Bloch แนะนำในรายการที่ 30 ของ 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();
}

แน่นอนคุณสามารถส่งผ่านพารามิเตอร์ไปยัง doCommand หรือมีประเภทการส่งคืน

โซลูชันนี้อาจไม่เหมาะอย่างยิ่งหากการใช้ doCommand ไม่ "พอดี" กับประเภท enum ซึ่งตามปกติเมื่อคุณต้องทำการแลกเปลี่ยน - ค่อนข้างคลุมเครือ


7

มีคำสั่ง enum:

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

หากคุณมีคำสั่งมากกว่าสองสามคำสั่งให้ใช้รูปแบบคำสั่งตามที่ตอบไว้ที่อื่น (แม้ว่าคุณจะสามารถเก็บ enum และฝังการเรียกไปยังคลาสการนำไปใช้งานภายใน enum แทนการใช้ HashMap) โปรดดูตัวอย่างคำตอบของ Andreas หรือ jens สำหรับคำถามนี้


5
สำหรับแต่ละคำสั่งใหม่ที่คุณเพิ่มคุณต้องแก้ไขสวิตช์: รหัสนี้ไม่เป็นไปตามหลักการเปิด / ปิด
dfa

ขึ้นอยู่กับว่าคำสั่งนั้นน้อยหรือมากใช่หรือไม่ นอกจากนี้ไซต์นี้ยังทำงานช้าลงอย่างน่าตกใจทุกวันนี้ต้องใช้เวลา 5 ครั้งในการแก้ไขคำตอบ
JeeBee

นี่ไม่ใช่วิธีที่ดีที่สุดโปรดดูstackoverflow.com/questions/1199646/…เกี่ยวกับวิธีการทำให้ดีที่สุด
Andreas Petersson

ใช่ขอขอบคุณที่สละเวลาในการใช้สิ่งที่ฉันเขียนไว้ที่ด้านล่างของความคิดเห็น - Java Enum เป็นรูปแบบคำสั่ง หากฉันสามารถแก้ไขโพสต์ได้ฉันจะพูดถึงสิ่งนี้ แต่ไซต์นี้กำลังจะตาย
JeeBee

ฉันคิดว่าคำถามนี้น่ากลัวสำหรับคำสั่ง Switch!
Michael Brown

7

การติดตั้งอินเทอร์เฟซตามที่แสดงให้เห็นอย่างเรียบง่ายและชัดเจนโดย dfa นั้นสะอาดและสวยงาม (และวิธีที่รองรับ "อย่างเป็นทางการ") นี่คือสิ่งที่แนวคิดของอินเทอร์เฟซมีไว้สำหรับ

ใน C # เราสามารถใช้ delegates สำหรับโปรแกรมเมอร์ที่ชอบใช้ functon pointers ใน 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();

+1 สำหรับการกล่าวถึงผู้รับมอบสิทธิ์ในกรณีที่มีคน. NET เห็นคำถามนี้และคลั่งไคล้กับอินเทอร์เฟซวิธีเดียว แต่มันเทียบไม่ได้กับฟังก์ชันพอยน์เตอร์ ใกล้เคียงกับรูปแบบคำสั่งเวอร์ชันที่รองรับภาษา
Daniel Earwicker


3

แม้ว่าฉันจะเชื่อว่าแนวทางรูปแบบคำสั่งนั้นเป็นแนวทางที่ดีที่สุดและสามารถบำรุงรักษาได้ในระยะยาว แต่นี่เป็นตัวเลือกหนึ่งสำหรับคุณ:

org.apache.commons.beanutils.MethodUtils.invokeMethod (ค่านี้ "doCommand" + null);


2

ฉันมักจะพยายามแก้ด้วยวิธีนี้:

public enum Command {

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

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

abstract void exec();
 }

สิ่งนี้มีข้อดีหลายประการ:

1) ไม่สามารถเพิ่ม enum ได้โดยไม่ต้องใช้ exec ดังนั้นคุณจะไม่พลาด A

2) คุณไม่จำเป็นต้องเพิ่มลงในแผนที่คำสั่งใด ๆ ด้วยซ้ำดังนั้นจึงไม่มีรหัสต้นแบบสำหรับการสร้างแผนที่ เพียงวิธีนามธรรมและการนำไปใช้ (ซึ่งเป็นเนื้อหาสำเร็จรูปเช่นกัน แต่จะไม่สั้นลงไปอีก .. )

3) คุณจะบันทึกวงจรซีพียูที่สูญเปล่าโดยดูรายการ if's หรือคำนวณ hashCodes และทำการค้นหา

แก้ไข: ถ้าคุณไม่มี enums แต่สตริงเป็นแหล่งที่มาให้ใช้Command.valueOf(mystr).exec()เพื่อเรียกเมธอด exec โปรดทราบว่าคุณต้องใช้ตัวปรับแต่งสาธารณะใน exec หากคุณต้องการเรียกใช้จากแพ็คเกจอื่น


2

คุณน่าจะดีที่สุดโดยใช้ Map of Commands

แต่คุณมีชุดของสิ่งเหล่านี้ที่จะจัดการกับคุณในที่สุดเมื่อมีแผนที่มากมาย แล้วมันก็คุ้มค่าที่จะทำกับ Enums

คุณสามารถทำได้ด้วย Enum โดยไม่ต้องใช้สวิตช์ (คุณอาจไม่จำเป็นต้องใช้ getters ในตัวอย่าง) หากคุณเพิ่มวิธีการลงใน Enum เพื่อแก้ปัญหาสำหรับ "value" จากนั้นคุณสามารถทำได้:

อัปเดต: เพิ่มแผนที่คงที่เพื่อหลีกเลี่ยงการทำซ้ำในการโทรแต่ละครั้ง pinched ลงคอจากคำตอบนี้

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และต้องการใช้ Lambdas!

คำสั่งที่ไม่มีพารามิเตอร์:

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 แทน Command ได้ แต่ฉันคิดว่ามันไม่ถูกต้องตามความหมาย):

คำสั่งที่มีพารามิเตอร์เดียว:

ในกรณีที่คุณต้องการพารามิเตอร์ที่คุณสามารถใช้ได้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คลาสของซึ่งใช้ Object ใด ๆ (ชื่อparamในตัวอย่างนี้) เป็นอาร์กิวเมนต์


1

ถ้าคุณมีงบ imbricated หลาย 'ถ้า' แล้วนี้เป็นรูปแบบสำหรับการใช้เครื่องมือการปกครอง ดูตัวอย่างเช่นJBOSS Drools



0

ถ้าเป็นไปได้ที่จะมีอาร์เรย์ของโพรซีเดอร์ (สิ่งที่คุณเรียกว่าคำสั่ง) ที่เป็นประโยชน์ ..

แต่คุณสามารถเขียนโปรแกรมเพื่อเขียนโค้ดของคุณได้ ทุกอย่างเป็นระบบมาก if (value = 'A') commandA (); อื่น ๆ ถ้า (........................ ฯลฯ


0

ฉันไม่แน่ใจว่าคุณมีความทับซ้อนระหว่างพฤติกรรมของคำสั่งต่างๆของคุณหรือไม่ แต่คุณอาจต้องการดูรูปแบบChain Of Responsibilityซึ่งสามารถให้ความยืดหยุ่นมากขึ้นโดยอนุญาตให้หลายคำสั่งจัดการกับค่าอินพุตบางอย่าง


0

รูปแบบคำสั่งเป็นวิธีที่จะไป นี่คือตัวอย่างหนึ่งที่ใช้ java 8:

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.