Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

JAVA Developer Training

커맨드 패턴 ( Command Pattern ) 본문

디자인패턴

커맨드 패턴 ( Command Pattern )

Romenest 2021. 10. 11. 18:12
커맨드 패턴

커맨드 패턴은 이벤트가 발생했을 시 실행될 기능들의 변경이 필요한 경우 이벤트를 발생시키는 클래스를 변경하지않고 재사용 하고자 할 때 유용하다 , 실행될 기능들을 캡슐화함으로써 주어진 여러 기능을 실행할 수 있는 재사용성이 높은 클래스를 설계하는 패턴이다.

커맨드를 추가하고 난 다음 Receiver(수신자), Invoker(발동자) 클래스만 수정하기만 하면 바로 추가된 커맨드를 사용할 수 있다.

*Receiver = 행동을 담당하는 객체 = 기능을 수행하는 객체

Invoker = 커맨드를 저장하는 객체 - 버튼이 어떤 수행을 할 것인지 결정

쉽게말해 실행된 이벤트(요청)들을 객체로 감싸서 관리한다는 말

사용 이유

다른 시간에 요청을 작성하고 실행해야할때나 롤백,로깅, *트랜잭션 기능을 지원할때 사용하면 편하다

*트랜잭션이란 데이터베이스의 상태를 변화시키기 위해서 수행하는 작업의 단위를 뜻한다

간단히 말해 SQL문을 이용하여 DB에 접근하는 것을 말한다

SELECT / INSERT / DELETE / UPDATE

단, 작업의 단위는 질의어 한문장이 아니라는 것을 상기해야한다 

- EX) SELECT * FROM STUDENT

*작업단위는 많은 질의어 명령문들을 사람이 정하는 기준에 따라 정하는 것을 의미한다

한문장이 아닌 경우를 들어보자면 게시판 사용시 

게시글 작성 -> 게시글 올리기 - > 이후 게시판을 보았을때 업데이트 된 게시판 보기

즉 , INSERT + SELECT 두가지 과정을 합친것이 하나의 작업단위가 된다.

장점
  • 기존 클래스는 그대로 유지되기 때문에 새명령(요청)을 쉽게 추가할 수 있다.
  • 조작을 호출하는 객체와 실제로 조작을 수행하는 객체 를 분리한다
  • 즉, 명령의 호출자와 수신자 간의 의존성을 없앤다
  • 요청내역을 큐에 저장하면 스레드 작업에 유용하다
단점
  • 명령에 대한 각각의 클래스는 많아져 복잡해 질 것이다.
예시
커맨드 패턴을 이해하기 쉽게 예시를 하나들어보자
우리가 음식점에 갔을때를 생각해보면 간단하다.
홀 아르바이트생이 와서 우리가 원하는 주문을 받아적는다. 예를들어
후라이드 치킨과, 양념치킨, 간장치킨과 콜라를 주문했다고 치자 그렇다면 아르바이트생은 포스기에 가서 작성할 것이다. 후라이드 치킨-1 , 양념치킨-1, 간장치킨-1, 콜라-1
입력한 데이터를 그대로 주문지로 뽑아 주방으로 전달 할 것이다.(수기로 주던 주방에서 프린트가 되어 나오던)
주방장은 주문지를 보고 요리를 만들 것이다.

위 예시에서 주문지는 받은 주문을 일종의 캡슐화 했다고 보면된다.

아르바이트생은 주문서의 내용을 사실 몰라도 된다. 손님들이 임의로 작성하여 그대로 주방장에게 주어도 무방하다는 뜻이다 . 왜냐? 이미 주문지에 다 적혀 있기 때문이다.

주방장은 손님이 누구인지 알필요도 없으며 손님또한 누가 음식을 만드는지 알필요가 없다.

요리 하는 방법은 주방장이 알고 있으며 해당 주문지에 손님들의 주문 요리가 있으니 문제가 없다는 말이다.

이 예제를 커맨드 패턴에 대입하면 아래와 같을 것이다.

식당 주문 시스템 커맨드 패턴
손님 클라이언트
아르바이트생 인보커 객체
주문을 받는것 setCommand()
주문지 커맨드 객체
주문을 주방장에게 전달하여 요리를 요청하는 것 execute()
주방장 리시버 객체
  1. 클라이언트는 커맨드 객체를 생성하고
  2. 클라이언트에서 인보커 객체 안의 setCommand() 를 호출 커맨드 객체를 넘겨준다
  3. 인보커 객체에서 커맨드 객체의 execute() 를 호출하면 리시버에 있는 특정 행동을 하는 메소드가 호출된다
  4. 이후 클라리언트에서 인보커에게 그명령을 실행시켜달라는 요청을 한다
예제 코드

이해하기 쉽게 다용도 리모컨을 커맨드 패턴으로 구현해 보겠다.

 

1. Command 인터페이스 정의

public interface Command {
 
    public void execute();
    
}

2. 리시버 객체(명령을 받는) 만들기

전등

public class Light {
    
    private String location;
    
    public Light(String location) {
        this.location = location;
    }
    
    public void on() { 
    	System.out.println(location + " 전등 켜짐"); 
    }
    public void off() {
    	System.out.println(location +" 전등 꺼짐"); 
    }
    
}

에어콘

public class AirConditioner {

    
    public void on() {
    	System.out.println("에어콘 켜짐"); 
        }
    public void off() {
    	System.out.println("에어콘 꺼짐"); 
        }
    
}

 

3. 인터페이스를 상속받는 커맨드 객체 만들기

전등 켜지는 커맨드 객체

public class LightOnCommand implements Command{
    Light light;

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

    @Override
    public void execute() {
       light.on();
    }
    
}

전등 꺼지는 커맨드 객체

public class LightOffCommand implements Command{
    Light light;

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

    @Override
    public void execute() {
       light.off();
    }
    
}

에어콘 켜지는 커맨드 객체

public class AirConditionerOnCommand implements Command {
    
    private AirConditioner airConditioner;
    
    public AirConditionerOnCommand(AirConditioner airConditioner) {
        this.airConditioner = airConditioner;
    }
    
    @Override
    public void execute() {
       airConditioner.on();
    }
    
    
}

에어콘 꺼지는 커맨드 객체

public class AirConditionerOffCommand implements Command {
    
    private AirConditioner airConditioner;
    
    public AirConditionerOffCommand(AirConditioner airConditioner) {
        this.airConditioner = airConditioner;
    }
    
    @Override
    public void execute() {
       airConditioner.off();
    }
    
    
}

빈명령 슬롯을 초기화 시키는 커맨드 객체

public class NoCommand implements Command {
 
    @Override
    public void execute() {
        System.out.println("명령 슬롯이 초기화 되어 있지 않습니다.");
        
    }
 
}

 

4. 인보커 객체(커맨드 객체를 저장하는) 만들기

public class RemoteControl {
    Command[] onCommands;
    Command[] offCommands;

    public RemoteControl() {
        onCommands = new Command[6];
        offCommands = new Command[6];
        NoCommand noCommand = new NoCommand();
        
        for(int i=0; i<6; i++){
        onCommands[i] = noCommand;
        offCommands[i] = noCommand;
        }
        //커맨드의 각슬롯을 모두다 비어있는상태로 초기화
    }
    //원하는 슬롯에 명령 부여
    public void setCommand(int slot,Command onCommand,Command offCommand) {
        onCommands[slot] = onCommand;
        offCommands[slot] = offCommand;
    }
    
    //슬롯번호 눌렀을때 해당커맨드 켜기
    public void onbuttonPressed(int slot) {
        onCommands[slot].execute();
    }
    
    //슬롯번호 눌렀을때 해당커맨드 끄기
    public void offbuttonPressed(int slot) {
        offCommands[slot].execute();
    }
}

 

5. 클라이언트 객체 만들기

public class TestRemote {
    public static void main(String[] args) {
        RemoteControl remoteControl = new RemoteControl();
        
        
        Light kitchenlight = new Light("주방");
        LightOnCommand kitchenlightOnCommand = new LightOnCommand(kitchenlight);
        LightOffCommand kitchenlightOffCommand = new LightOffCommand(kitchenlight);
        
        Light bedroomlight = new Light("침실");
        LightOnCommand bedroomlightOnCommand = new LightOnCommand(bedroomlight);
        LightOffCommand bedroomlightOffCommand = new LightOffCommand(bedroomlight);
        
        AirConditioner ariConditioner = new AirConditioner();
        AirConditionerOnCommand ariConditionerOnCommand = new AirConditionerOnCommand(ariConditioner);
        AirConditionerOffCommand ariConditionerOffCommand = new AirConditionerOffCommand(ariConditioner);
        
    
        remoteControl.setCommand(0,kitchenlightOnCommand,kitchenlightOffCommand);
        //0번커맨드에는 주방의 불을 끄고 키는 명령을 심어둔다
        remoteControl.setCommand(1,bedroomlightOnCommand,bedroomlightOffCommand);
        //1번커맨드에는 침실의 불을 끄고 키는 명령을 심어둔다
        remoteControl.setCommand(2, ariConditionerOnCommand,ariConditionerOffCommand);
        //2번커맨드에는 에어콘을 키고 끄는 명령을 심어둔다
        
        remoteControl.onbuttonPressed(0);
        remoteControl.onbuttonPressed(1);
        remoteControl.onbuttonPressed(2);
        
        remoteControl.offbuttonPressed(0);
        remoteControl.offbuttonPressed(1);
        remoteControl.offbuttonPressed(2);
        
        remoteControl.onbuttonPressed(5);
        remoteControl.offbuttonPressed(5);
        
        //이후 버튼을 눌러 execute() 실행
    }
}

원하는 형태

결과

마무리 정리

커맨드 패턴은 이벤트를 발생시키는 클래스를 변경하지않고 언제든지 재사용 할수 있게 해주는 디자인 패턴이다

만약 위 예제코드에서 화장실 전등을 껏다 키는 명령을 추가하고싶다면 Receiver(수신자), Invoker(발동자) 클래스만 추가,수정 한다면 간편하게 등록이 가능하다.

또한 요구사항이 캡슐화 되므로 코드중복을 방지할 수 있다.