자바의 if 문의 긴 목록


101

이 질문에 대한 답을 찾을 수 없어서 죄송합니다. 다른 사람이 이전에 질문을 제기 한 것 같습니다.

내 문제는 임베디드 장치를 실행하기 위해 일부 시스템 라이브러리를 작성하고 있다는 것입니다. 라디오 방송을 통해 이러한 장치로 보낼 수있는 명령이 있습니다. 이것은 텍스트로만 수행 할 수 있습니다. 시스템 라이브러리 내부에는 다음과 같은 명령을 처리하는 스레드가 있습니다.

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

문제는 그것에 대한 많은 명령이 통제 불능으로 빠르게 나선다는 것입니다. 밖을 내다 보는 것은 끔찍하고 디버그하는 것은 고통스럽고 몇 달 안에 이해하기가 벅차 오릅니다.


16
그냥 코멘트-나는 Gang of Four patterns 책을 선택하거나 패턴을 처음 접하는 경우 Head First Design Patterns in Java 책 (아주 쉽게 읽을 수 있고 여러 일반적인 패턴에 대한 훌륭한 소개서)을 선택하는 것이 좋습니다. ). 둘 다 귀중한 자원이며 둘 다 내 베이컨을 두 번 이상 절약했습니다.
aperkins 2009-07-29

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 열거 형을 사용할 수 있습니다. 열거 형에 getter가있을 수 있습니다. Command getCommand (); 또는 각 인스턴스가 구현하는 (enum as command) enum의 추상 메서드로 exec ()를 구현할 수도 있습니다.
JeeBee

2
이것은 열거 형의 모든 명령을 구현하도록 강제합니다 ... 그것은 이상적이지 않습니다. 인터페이스를 사용하면 Decorator 패턴 (예 : DebugCommandDecorator, TraceCommandDecorator)도 적용 할 수 있습니다. 단순한 Java 인터페이스에 훨씬 더 많은 유연성이 내장되어 있습니다.
dfa

5
자, 작고 성장하지 않는 명령 세트의 경우 열거 형은 실행 가능한 솔루션입니다.
dfa 2009-07-29

12

내 제안은 enum과 Command 객체의 가벼운 조합입니다. 이것은 Effective Java의 항목 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의 구현이 enum 유형에 실제로 "적합"하지 않은 경우에는 적합하지 않을 수 있습니다. 이는 일반적으로 트레이드 오프를해야 할 때처럼 약간 모호합니다.


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

몇 개 이상의 명령이있는 경우 다른 곳에서 대답 한대로 Command 패턴을 사용하는 것이 좋습니다 (HashMap을 사용하는 대신 enum을 유지하고 enum 내에 구현 클래스에 대한 호출을 포함 할 수 있음). 이 질문에 대한 Andreas 또는 jens의 답변을 참조하십시오.


5
추가하는 각각의 새 명령에 대해 스위치를 편집해야합니다.이 코드는 열기 / 닫기 원칙을 따르지 않습니다
dfa

명령이 적은지 또는 많은지에 따라 다릅니다. 또한이 사이트는 요즘 너무 느려서 답변을 편집하는 데 5 번의 시도가 필요합니다.
JeeBee

이것은 최적이 아닙니다. stackoverflow.com/questions/1199646/… 을 참조하십시오 .이 작업을보다 최적화하는 방법에 대해 알아보십시오.
Andreas Petersson

예, 제 의견의 맨 아래에 작성한 Java Enum as Command Pattern을 구현하는 데 시간을 투자 해 주셔서 감사합니다. 내 게시물을 편집 할 수 있다면 이것을 언급하지만이 사이트는 죽어 가고 있습니다.
JeeBee

이 질문은 Switch 성명서에 비명을 지르고 있다고 생각합니다!
Michael Brown

7

dfa에 의해 간단하고 명확하게 입증 된대로 인터페이스를 구현하는 것은 깨끗하고 우아합니다 (그리고 "공식적으로"지원되는 방식). 이것이 인터페이스 개념의 의미입니다.

C #에서는 c에서 functon 포인터를 사용하려는 프로그래머를 위해 대리자를 사용할 수 있지만 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. 그러나 그들은 실제로 함수 포인터와 비교할 수 없습니다. 명령 패턴의 언어 지원 버전에 더 가깝습니다.
Daniel Earwicker

5

글쎄, 나는 명령 객체를 만들고 String as Key를 사용하여 해시 맵에 넣는 것이 좋습니다.


3

명령 패턴 접근 방식이 최선의 방법에 더 가깝고 장기적으로 유지 관리 할 수 ​​있다고 생각하더라도 여기에 한 가지 라이너 옵션이 있습니다.

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 사이클을 저장합니다.

편집 : enum이 없지만 문자열이 소스로 있으면 Command.valueOf(mystr).exec()exec 메서드를 호출하는 데 사용 하십시오. 다른 패키지에서 호출하려는 execif에 public 한정자를 사용해야합니다.


2

명령 맵을 사용하는 것이 가장 좋습니다.

그러나 당신은 당신을 처리하기 위해 이것들의 세트를 가지고 있습니까? 그런 다음 Enum으로 수행하는 것이 좋습니다.

"값"을 확인하기 위해 Enum에 메서드를 추가하는 경우 스위치를 사용하지 않고 Enum을 사용할 수 있습니다 (예제에서 getter가 필요하지 않을 수 있음). 그런 다음 다음을 수행 할 수 있습니다.

업데이트 : 각 호출에서 반복을 피하기 위해 정적 맵을 추가했습니다. 이 답변 에서 뻔뻔스럽게 꼬집었습니다 .

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 을 사용 중이고 Lambda를 사용하려는 경우를 대비 하여 몇 가지 스 니펫 제공 하고 있습니다!

매개 변수가없는 명령 :

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

(Command 대신 Runnable을 사용할 수 있지만 의미 상 옳다고 생각하지 않습니다) :

하나의 매개 변수가있는 명령 :

매개 변수가 필요한 경우 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);

위의 예에서는 ( 이 예에서 이름 이 지정된) Object를 인수로 취하는의 클래스 doSomethingX에있는 메서드 입니다.myObjparam


1

여러 개의 내재 된 'if'문이있는 경우 이는 규칙 엔진 을 사용하기위한 패턴입니다 . 예를 들어 JBOSS Drools 를 참조하십시오 .



0

일련의 프로 시저 (명령이라고 부르는 것)를 가질 수 있다면 유용 할 것입니다 ..

하지만 코드를 작성하는 프로그램을 작성할 수 있습니다. 모두 매우 체계적입니다. if (value = 'A') commandA (); else if (........................ etc


0

다양한 명령의 동작 사이에 겹치는 부분이 있는지 확실하지 않지만 여러 명령이 일부 입력 값을 처리하도록 허용하여 더 많은 유연성을 제공 할 수있는 책임사슬 패턴을 살펴볼 수도 있습니다.


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.