디자인 패턴

[디자인 패턴] 커맨드 패턴(Command Pattern)

제리 . 2021. 5. 5. 00:12

커맨드 패턴이란?

간단히 말하자면 커맨드 패턴은 요청을 객체의 형태로 캡슐화하여 처리하는 방식이다. 일반적으로 객체는 메서드를 통해 행동을 정의하지만, 행동 자체를 객체로서 표현한다는 의미이다. 

 

만약, 전등을 객체로 표현하고 전등이 켜지는 행위는 아래와 같이 표현된다.

public class Light {

  public void on() {
    System.out.println("전등 켜짐");
  }

}

커맨드 패턴에 따라 on()동작 자체를 객체로서 정의하면 아래와 같은 코드가 된다.

public interface Command {
  void execute();
}

public class Light {

  public void on() {
    System.out.println("전등 켜짐");
  }

}

public class LightOnCommand implements Command{
  Light light;
  
  public LightOnCommand(Light light){
  	this.light = light;
  }
  
  @Override
  public void execute(){
  	light.on();
  }
}


LightOnCommand객체는 전등 켜기 동작을 나타낸다.

 

왜 이렇게 표현하는 것이 좋을까?

커맨드 패턴이 효율적인 이유는 기능의 확장에 연관있다. 위의 코드를 활용해서 리모컨으로 전등을 키는 동작을 예시로 들어보겠다.

 

아래는 커맨드 패턴을 사용하지 않고 리모컨으로 전등을 키는 동작을 정의한 코드이다.

public class Light {

  public void on() {
    System.out.println("전등 켜짐");
  }

}

public class RemoteControl {

  Light light;

  public RemoteControl(Light light) {
    this.light = light;
  }

  public void buttonWasPressed() {
    light.on();
  }
}


public class Main{

    public void static main(String[] args){
        RemoteControl remoteControl = new RemoteControl(new Light());
        remoteControl.buttonWasPressed();
    }
}

여기서 전등과 음악을 켜도록 리모콘이 동작하게 하려면 어떻게 해야할까? 아마 아래와 같이 Music클래스를 정의하고 RemoteControl클래스에서 동작을 추가하면 될 것이다.

public class Light {

  public void on() {
    System.out.println("전등 켜짐");
  }

}

public class Music {

  public void on() {
    System.out.println("음악 켜짐");
  }
}

public class RemoteControl {

  Light light;
  Music music;

  public RemoteControl(Light light, Music music) {
    this.light = light;
    this.music = music;
  }

  public void lightOnbuttonWasPressed() {
    light.on();
  }
  
  public void musicOnButtonWasPressed(){
  	music.on();
  }
}


public class Main{

    public void static main(String[] args){
        RemoteControl remoteControl = new RemoteControl(new Light(), new Music());
        remoteControl.lightOnbuttonWasPressed();
        remoteControl.musicOnButtonWasPressed();
    }
}

그렇다면 전등, 음악 이외에도 TV, 에어컨 작동등 새로운 기능이 추가되면 어떻게 될까? RemoteControl클래스의 코드를 매번 수정해서 기능을 추가해야한다. 이는 OCP원칙에 위배되는 행위이다.  

 

커맨드 패턴

 

 

 

  • Main클래스는 요청을 보내는 Client에 해당한다.
  • 클라이언트에서 버튼을 누를 때 RemoteControl객체에 메서드를 실행한다. RemoteControl객체는 클라이언트의 요청을 받아전달하는 Invoker역할을 한다.
  • Light는 클라이언트의 요청이 실제로 행해지는 on()메서드를 가지고 있다. Light는 요청받은 것을 수행하는 Receiver역할을 한다. 
  • Command는 execute()메서드를 가지는 인터페이스이다. command가 가져야할 동작을 정의한다.
  • LightOnCommand는 Command인터페이스의 execute()를 구현한다. 이 객체는 행위를 나타내는 ConcreteCommand이다.

자, 그렇다면 커맨드 패턴이 코드로 어떻게 구현되었는지 확인해보자.

public interface Command {
  void execute();
}


public class LightOnCommand implements Command{
  Light light;
  
  public LightOnCommand(Light light){
  	this.light = light;
  }
  
  @Override
  public void execute(){
  	light.on();
  }
}


public class Light {

  public void on() {
    System.out.println("전등 켜짐");
  }

}


public class RemoteControl {

  Command command;

  public RemoteControl() {
    
  }

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

  public void buttonWasPressed() {
    command.execute();
  }
}


public class Main{

    public void static main(String[] args){
    	LightOnCommand lightOnCommand = new LightOnCommand(new Light());
        RemoteControl remoteControl = new RemoteControl();
        remoteContorl.setCommand(lightOnCommand); //커맨드 설정
        remoteControl.buttonWasPressed();
    }
}

커맨드 패턴은 객체의 동작에 대한 객체를 정의한다. 그리고 동작을 정의한 객체를 Command로서 사용하여 동작하도록 한다. 객체의 동작은 Command인터페이스를 상속받으므로서 동일한 동작을 보장할 수 있다. 이를 테면 LightOnCommand, LightOffCommand, MusicOnCommand등등 Command인터페이스의 excute를 구현하고 있기 때문에 RemoteControl객체에서는 클래스의 구분 없이 구현한 동작이 실행하게 할 수 있다. 커맨드 패턴은 RemoteControl 클래스에서 코드 변경없이도 새로운 기능을 수행할 수 있다.

 

 

만약, 새롭게 기능을 확장하는 상황이라면?

...

public class Music {

  public void on() {
    System.out.println("음악 켜짐");
  }
}
 
public class MusicOnCommand implements Command{
  Music music;
  
  public MusicOnCommand(Music music){
  	this.music = music;
  }
  
  @Override
  public void execute(){
  	music.on();
  }
}

public class Main{

    public void static main(String[] args){
        LightOnCommand lightOnCommand = new LightOnCommand(new Light());
        MusicOnCommand musicOnCommand = new MusicOnCommand(new Music()); 
        RemoteControl remoteControl = new RemoteControl();
        remoteContorl.setCommand(lightOnCommand); //전등 켜기 커맨드 설정
        remoteControl.buttonWasPressed();
        remoteContorl.setCommand(musicOnCommand); //음약 켜기 커맨드 설정
        remoteControl.buttonWasPressed();
    }
}

새로운 커맨드 객체를 만들고 Main(client)에서 커맨드를 바꿔서 실행해주면 된다. 기능이 확장되면서 변경되어야하는 점은, 동작에 따른 새로운 객체를 만들고 client에서는 변경된 커맨드를 사용하기만하면 기존의 구조를 유지하면서도 새로운 동작을 정의할 수 있다.