언어/디자인패턴

디자인 패턴 (7) - 커맨드 패턴

now0204 2023. 9. 12. 11:31

 

1. 커맨드 패턴이란?

 

- 실행될 기능을 캡슐화하여 여러 기능을 실행할 수 있는 재사용성이 높은 클래스 설계 패턴

- 이벤트가 발생했을 때 실행될 기능이 다양+변경 가능성이 있을때, 이벤트 발생 클래스 변경하지 않고 재사용

- 행위 패턴 중 하나이다.

- 실행할 기능 캡슐화하여 실행 요구 호출자(클라이언트)클래스와 실제 기능 수신자 클래스 의존성 제거

- 실행될 기능이 변경되어도 호출자 클래스에 어떠한 수정 없이 그대로 사용가능

 

 

1.1 커맨드 패턴의 구조 

 

출처:https://gmlwjd9405.github.io/2018/07/07/command-pattern.html

- Command : 실행될 기능에 대한 인터페이스 

- ConcreteCommand: 실제 실행되는 기능 구현

- Invoker : 기능의 실행을 요청하는 호출자 클래스

- Receiver: ConcreteCommand에서 execute 메서드 구현시 필요한 클래스 

                  즉, ConcreteCommand 기능 실행하기 위해 사용됨

 

* 클라이언트가 커맨드를 만든다 -> 인보커에 set커맨드한다 -> 인보커는 커맨드는 execute를 호출 ->

execute된 콘크리트 커맨드는 구성된 객체 메서드 호출  

 

*일련의 행동을 특정 리시버와 연결하여 요청을 캡슐화 (객체를 서로 다른 요청에 따른 매개변수화)

 

 

2. 만능버튼 만들기 (예시1)

 

public class Lamp{
	public void turnOn(){//램프켜짐}
}

//버튼을 누르면 램프의 불이 켜짐 
public class Button{ //호출자
 private Lamp lamp;
 public Button(Lamp la) {this.lamp = la;}
 public void pressed(){
 	la.trunOn();
 }
}

public class Client{
	public static void main(String[] args){
    	Lamp lamp = new Lamp();
        Button lampButton = new Button(lamp);
        lampButton.pressed();
    }

}

- Button을 눌렀을 때 불이 켜지는 이벤트 

 

2.1 문제점 

 

1. 버튼을 눌렀을 때 다른 기능을 실행하는 경우 

ex 버튼 눌렀을 때 알람이 시작되게 하려면?

public class Alarm{
	public void start(){
    	//알람울림
    }
}

public class Button{
	private Alarm alarm;
    public Button(Alarm al){this.alarm = al;}
	public void pressed() {alarm.start();}
}

 - 새로운 기능이 추가되면, 기존 Button내용을 수정해야함 

 

2. 버튼을 누르는 동작에 따라 다른 기능 실행해야하는 경우 (이게 포인트)

public class Button{
	private Lamp lamp;
    private Alarm al;
    
    public Button() //램프와 알람을 생성자에서 받음
    
    public void pressed(String mode){
    	//mode에따라 lamp에 불을 켜거나 알람을 울림 
        //새로운 기능이 추가되면, 버튼을 수정해야함 
        //따라서 OCP위배
    }

}

 

2.2 커맨드패턴 적용

 

- 기능-> 버튼 눌렀을 때 이벤트가 변화가능성이 있다 -> 캡슐화 

- Button클래스의 pressed메서드 구체적 기능을 직접구현하지 않고, 캡슐화된 기능을 외부에서 제공받아서 

  위임하자!

출처:https://gmlwjd9405.github.io/2018/07/07/command-pattern.html

- Button클래스는 Command 인터페이스를 구성하고 execute를 호출

- LampOnCommand는 램프 온되도록 execute를 구현

- AlarmStartCommand는 알람이 켜지도록 구성 

 

> Command와 Button

public interface Command{
	public void execute();
}

public class Button{
	private Command co;
    public Button(Command co) {
    
    setCommand(co);
    
    }
    public void setCommand(command newCommand){
    	this.co = newCommand;
    } 
    
    public void pressed(){
    	co.execute();
    }

}

-Lamp와 LampOnCommand

public class Lamp{

	public void turnOn(){//}

}

public class LampOnCommand implements Command{
	private Lamp lamp;
    public LampOnCommand(Lamp la){
    	this.lamp=la;
    }
    public void execute(){
    	lamp.turnOn();
    }
}

- Client

public class Client{

public static void main(String[] args){
	Lamp lamp = new Lamp();
    Command lampOnCommand = new LampOnCOmmand(lamp);
    Button button1 = new Button(lampOnCommand);
    button1.pressed();

}

}

 

*Invoker는 커맨드를 가지고 있고, 커맨드는 Receiver를 구성함 

 -> 커맨드를 호출하면, 커맨드가 콘크리트가 구성한 Receiver의 메서드를 호출 

 

* 전략패턴은 보통 최상위 Abstract클래스가 전략인터페이스와 전략을 수정할 방법을 가지고 있고,

  새로운 구체 클래스를 만든 후 전략을 수정함 (추상화된 놈이 초기 전략 가짐 -> 구체는 경우에 따라 수정)

* 커맨드 패턴은 특정 클래스에서 전략 인터페이스를 사용하는데, 구체 전략에 따라 전략과 관련된 클래스가 맵핑되는 느낌 이런차이 아닐까?