커맨드 패턴 Command Pattern

Bam·2022년 3월 5일
0

Design Pattern

목록 보기
6/13
post-thumbnail
post-custom-banner

커맨드 패턴

커맨드 패턴은 특정 기능들을 캡슐화 시키는 패턴입니다. 캡슐화는 객체지향 프로그래밍의 개념 중 하나로, 관련있는 변수/함수를 하나의 클래스로 묶습니다. 그리고 외부에 은닉하는 것을 캡슐화라고 합니다. 커맨드 패턴은 행동 패턴에 속합니다.

그래서 보통 매개변수를 이용해서 기능에 다른 요구 사항들을 넣을 수 있게됩니다. 예를 들어 전원을 켜는 기능이 있다고 할 때, 매개변수로 티비냐 컴퓨터냐에 따라 같은 전원을 켜는 기능이라 하더라도 티비를 키거나 컴퓨터를 키는 등의 동작이 가능해집니다.

커맨드 패턴에서는 크게 Invoker(이하 인보커, 호출자), Receiver(이하 리시버, 수신자), Command(이하 커맨드, 명령) 객체로 구분됩니다. 인보커는 기능의 실행을 요청하고 리시버는 명령을 수행하는 객체입니다.

커맨드 패턴 구현

커맨드 객체

먼저 커맨드 객체를 구현해보겠습니다. 커맨드 객체들은 모두 같은 인터페이스 아래서 구현되어야 합니다. 그 중심을 잡아주는 인터페이스가 바로 커맨드 인터페이스입니다. 그런데 이 인터페이스에는 독특하게도 단 하나의 메소드만이 있습니다. 위의 예시를 구현해보자면 On이라는 이름도 좋지만 보통 execute()라고 메소드 이름을 짓습니다.

public interface Command {
    public void execute();
}

이제 인터페이스를 바탕으로 커맨드 객체를 구현하려고 합니다. 위의 예시를 빌려 컴퓨터를 켜는 커맨드와 끄는 커맨드 클래스를 구현해보겠습니다.

public class ComputerOnCommand implements Command{
    private Computer computer;

    public ComputerOnCommand(Computer computer) {
        this.computer = computer;
    }
    
    @Override
    public void execute() {
        computer.turnOn();
    }
}
public class ComputerOffCommand implements Command {
    private Computer computer;

    public ComputerOffCommand(Computer computer) {
        this.computer = computer;
    }
    
    @Override
    public void execute() {
        computer.turnOff();
    }
}

두 커맨드 클래스의 전체적인 구성은 같습니다. 하지만 인터페이스로부터 오버라이드된 execute 함수를 보면 같은 이름을 가지고 각기 커맨드 클래스에 맞는 동작을 하게 되어있습니다.

리시버 객체 구현

위의 예시대로라면 리시버는 Computer가 됩니다. 리시버가 커맨드의 명령을 수행하는 역할이랬으니 execute에 따라 켜고 끄는 대상이 Computer이기 때문입니다.

public class Computer {
    public void Computer() {}

    public void turnOn() {
        System.out.println("컴퓨터 전원 켜짐");
    }

    public void turnOff() {
        System.out.println("컴퓨터 전원 꺼짐");
    }
}

위의 예시에서는 켜고 끄는 명령만을 탑재했기 때문에 비교적 간단한 구현이 되었습니다.

커맨드 객체를 사용할 객체 구현

이제 커맨드가 완성되었으니 커맨드 객체를 이용할 객체를 만들어야합니다. 말이 거창하지만 기계를 조작할 버튼이나 리모콘이라고 생각하면 쉽습니다.

public class Button {
    private Command command;

    public Button(Command command) {
        this.command = command;
    }

    public void setCommand(Command command) {
        this.command = command;
    }

    public void pressButton() {
        this.command.execute();
    }
}

여기서 주목할 점은 setCommand()와 pressButton() 메소드입니다.

우선 setCommand() 메소드는 커맨드를 변경하는 역할을 합니다. 예시대로라면 처음에 커맨드에 컴퓨터를 켜는 커맨드를 입력했다면 컴퓨터를 끄기 위해서 커맨드를 컴퓨터 종료 커맨드로 바꿔서 execute해야하는 작업이 필요한데 setCommand가 그러한 역할을 하고 있습니다.

다음으로 pressButton()은 execute() 즉, 커맨드를 실행하는 명령입니다. this.command 현재의 커맨드 객체의 execute()를 실행하겠다는 의미입니다.

메인 메소드

이제 기능을 요청할 인보커가 포함된 메인 메소드를 구현해보겠습니다.

public static void main(String[] args) {
    Computer computer = new Computer();	//컴퓨터는 리시버

	//컴퓨터 객체 생성
    ComputerOnCommand computerOnCmd = new ComputerOnCommand(computer);
    ComputerOffCommand computerOffCmd = new ComputerOffCommand(computer);

    Button btn = new Button(computerOnCmd);	//버튼이 인보커 역할
    btn.pressButton();
    btn.setCommand(computerOffCmd);
    btn.pressButton();
}

리시버인 컴퓨터 객체, 컴퓨터를 켜는 커맨드 객체와 컴퓨터를 끄는 커맨드 객체를 생성합니다. 그리고 커맨드 객체들을 조작할 버튼 객체를 만드는데 이때 버튼이 커맨드 객체를 받아서 기능 실행을 요청하므로 인보커가 됩니다. 그래서 처음 선언에 on커맨드를 넣었다면 "컴퓨터 켜짐"이 될 것이고, 이후 setCommand로 off커맨드를 넣었으니 그 다음 exectue에서는 "컴퓨터 꺼짐"이 될 것 입니다.

커맨드 패턴의 장단점

장점

  • 인보커와 리시버, 커맨드가 각각 캡슐화 되어서 결합도가 낮아진다.

단점

  • 리시버 객체의 동작이 늘어날 때 마다 커맨드 클래스가 늘어나기 때문에 클래스가 많아진다.

만들면서 봤듯이 on과 off에 각각 커맨드 클래스를 작성했습니다. 이제 여기서 선풍기에 미풍, 약풍, 강풍 버튼을 추가하면 세 개의 클래스가 더 추가되겠죠? 이런식으로 동작이 늘면 클래스가 무수히 늘어나는 단점도 존재합니다.

post-custom-banner

0개의 댓글