본문 바로가기

JAVA/Design Patterns

6.커맨드 패턴

반응형

커맨드 패턴

=>호출캡슐화


메서드 호출을 캡슐화하면 계산 과정의 각 부분들을 결정화시킬수 있기 때문에, 계산하는 코드를 호출한 객체에서는 어떤 식으로 일을 처리해야하는지에 대해 전혀 신경쓰지 않아도 된다.


정의 - 요구사항을 객체로 캡슐화할 수 있으며, 매개변수를 써서 여러가지 다른 요구사항을 집어넣을 수 있다.

요청 내역을 큐에 저장하거나 로그를 기록할 수 있으며 작업취소 기능도 지원 가능하다.


커맨드 객체는 일련의 행동을 특정 리시버하고 연결시킴으로써 요구 사항을 캡슐화한 것.

이렇게 하기 위해 행동과 리시버를 한 객체에 집어넣고 execute() 라는 메서드 하나만 외부에 공개하는 방법을 쓴다.

이 메서드 호출에 의해 리시버는 일련의 작업이 처리된다.

외부에서 볼땐 어떤 객체가 리시버 역할을 하는지, 그 리시버에서 실제로 어떤 일을 하는지 알수 없다.

execute() 메서드를 호출하면 요구사항이 처리된 것만 알수 있을 뿐이다.





package patterns.command;


public interface Command {

    public void execute();

}



package patterns.command;


public class LightOnCommand implements Command{

    private Light light;


    public LightOnCommand(Light light) {

        this.light = light;

    }

    @Override

    public void execute() {

        light.on();

    }

}


package patterns.command;


public class Light {


    public void on() {

        System.out.println("켜다");

    }

}



package patterns.command;


public class SimpleRemoteControl {

    private Command slot;


    public void setCommand(Command command) {

        slot = command;

    }


    public void buttonWasPressed() {

        slot.execute();

    }


}


package patterns.command;


public class RemoteControlTest {

    public static void main(String[] args) {

        SimpleRemoteControl remote = new SimpleRemoteControl(); //invoker 역할

        Light light = new Light();  //리시버

        LightOnCommand lightOnCommand = new LightOnCommand(light); //커맨드객체생성 리시버를 전달

        remote.setCommand(lightOnCommand); //커맨드객체를 인보커에 전달

        remote.buttonWasPressed(); //인보커 실행


        //추가

        GarageDoor garageDoor = new GarageDoor();

        GarageDoorOpenCommand garageDoorOpenCommand = new GarageDoorOpenCommand(garageDoor);

        remote.setCommand(garageDoorOpenCommand);

        remote.buttonWasPressed();


    }

}



package patterns.command;


public class GarageDoor {


    public void open() {

        System.out.println("열기");

    }

}


package patterns.command;


public class GarageDoorOpenCommand implements Command {

    private GarageDoor garageDoor;


    public GarageDoorOpenCommand(GarageDoor garageDoor) {

        this.garageDoor = garageDoor;

    }


    @Override

    public void execute() {

        garageDoor.open();

    }

}



예제


package patterns.command;


public class RemoteControl {

    Command[] onCommands;

    Command[] offCommands;


    public RemoteControl() {

        onCommands = new Command[7];

        offCommands = new Command[7];


        Command noCommand = new NoCommand();

        for(int i=0; i< 7; i++) {

            onCommands[i] = noCommand;

            offCommands[i] = noCommand;

        }

    }


    public void setCommand(int slot, Command onCommand, Command offCommand) {

        onCommands[slot] = onCommand;

        offCommands[slot] = offCommand;

    }


    public void onButtonPress(int slot) {

        onCommands[slot].excute();

    }

    public void offButtonPress(int slot) {

        offCommands[slot].excute();

    }


    @Override

    public String toString() {

        StringBuffer stringBuffer = new StringBuffer();

        stringBuffer.append("\n====remote control====\n");

        for (int i =0; i< onCommands.length; i++) {

            stringBuffer.append("[slot "+ i +"] " + onCommands[i].getClass().getName()

                +"              " + offCommands[i].getClass().getName() + "\n");

        }

        return stringBuffer.toString();

    }

}



package patterns.command;


public class RemoteLoader {

    public static void main(String[] args) {

        RemoteControl remoteControl = new RemoteControl();


        Light livingRoomLight = new Light("LivingRoom");

        Light kitchenLight = new Light("kitchen");

        GarageDoor garageDoor = new GarageDoor("");

        Stereo stereo = new Stereo("LivingRoom");


        LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);

        LightOffConmmand livingRoomLightOff = new LightOffConmmand(livingRoomLight);

        LightOnCommand kitchenLightOn = new LightOnCommand(kitchenLight);

        LightOffConmmand kitchenLightOff = new LightOffConmmand(kitchenLight);


        StereoOnWithCDCommand stereoOnWithCD = new StereoOnWithCDCommand(stereo);

        StereoOffCommand stereoOff = new StereoOffCommand(stereo);


        remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);

        remoteControl.setCommand(1, kitchenLightOn, kitchenLightOff);

        remoteControl.setCommand(2, stereoOnWithCD, stereoOff);


        System.out.println(remoteControl);


        remoteControl.onButtonPress(0);

        remoteControl.offButtonPress(0);

        remoteControl.onButtonPress(1);

        remoteControl.offButtonPress(1);

        remoteControl.onButtonPress(2);

        remoteControl.offButtonPress(2);

    }

}



    public void onButtonPress(int slot) {

        onCommands[slot].excute();

    }

    

    public void onButtonPress(int slot) {

    if(onCommands[slot] != null) {

        onCommands[slot].excute();

        }

    }

    널체크를 해야되지만 널객체를 사용하면 널체크 할 필요가 없다.


null 객체 패턴

딱히 리턴할 객체는 없지만 클라이언트쪽에서 null을 처리하지 않아도 되도록 하고싶을 때 널 객체를 활용


매크로 커맨드

package patterns.command;


public class MacroCommand implements Command {

    Command[] commands;


    public MacroCommand(Command[] commands) {

        this.commands = commands;

    }


    @Override

    public void excute() {

        for (int i=0; i< commands.length; i++) {

            commands[i].excute();

        }

    }


    @Override

    public void undo() {

        for (int i=0; i< commands.length; i++) {

            commands[i].undo();

        }

    }

}


public class RemoteLoader {

    public static void main(String[] args) {

        RemoteControl remoteControl = new RemoteControl();


        Command[] partyOn = { livingRoomLightOn, kitchenLightOn, stereoOnWithCD};

        Command[] partyOff= { livingRoomLightOff, kitchenLightOff, stereoOff};


        MacroCommand partyOnMacro = new MacroCommand(partyOn);

        MacroCommand partyOffMacro = new MacroCommand(partyOff);


        remoteControl.setCommand(4, partyOnMacro, partyOffMacro);

        remoteControl.onButtonPress(4);

        remoteControl.offButtonPress(4);

        System.out.println(remoteControl);

        remoteControl.onButtonPress(4);

        remoteControl.undoButtonPress();

        System.out.println(remoteControl);

}

}


커맨드 패턴 활용: 요청을 큐에 저장

커맨드를 이용하면 컴퓨테이션의 한부분(리시버와 일련의 행동)을 패키지로 묶어서 일급 객체 형태로 전달하는 것도 가능하다. 그렇게 하면 어떤 클라이언트 애플리케이션에서 커맨드 객체를 생성하고 나서 한참 후에도 그 컴퓨테이션을 호출할 수 있다. 심지어 다른 스레드에서 호출하는 것도 가능하다.

이런 시나리오는 스케줄러나 스레드 풀, 작업 큐와 같은 다양한 용도에 적용할 수 있다.


큐 한쪽 끝은 커맨드를 추가할 수 있도록 되어있고, 그 큐의 다른쪽 끝에는 커맨드를 처리하기 위한 스레드들이 대기하고 있다.

각 스레드에서 우선 execute() 메서드를 호출하고, 그 호출이 완료되고 나면 커맨드 객체를 보내 버리고 새로운 커맨드 객체를 가져온다.


작업 큐 클래스는 컴퓨팅 작업을 하는 객체들과 완전히 분리되어 있음을 알수 있다.

한 스레드에서 한동안 금융관련 계산을 하다가 잠시 후에는 네트워크를 통해 뭔가를 다운로드하고 있을 수 있다.

작업 큐 객체에서는 전혀 신경을 쓸 필요가 없다.

커맨들르 가져오고 execute()만 호출하면된다. 큐에 커맨드 패턴을 구현하는 객체를 집어 넣기만 하면 그 객체를 처리할 수 있는 스레드가 생기게 되고 자동으로 그 execute() 메서드가 호출될것이다.


요청을 로그에 기록하기

어떤 애플리케이션에서는 모든 행동을 기록해놨다가 그 애플리케이션이 다운되었을 경우, 나중에 그 행동을 다시 호출해서 복구 할 수 있도록 해야한다.

커맨드 패턴을 사용하면 store()와 load()라는 메서드를 추가하여 기능을 지원할 수 있다.

자바에서는 이런 메서드를 객체 직렬화를 통해 구현할수도 있지만, 직렬화와 관련된 제약 조건 때문에 일이 그리 쉽지 않을 수 있다.

로그기록? 어떤 명령을 실행하면서 디스크에 실행 히스토리를 기록하면된다.

애플리케이션이 다운되면 커맨드 객체를 다시 로딩하고 execute() 메서드들을 자동으로 순서대로 실행하면된다.

데이터가 변경될때마다 매번 저장할 수 없는 방대한 자료 구조를 다루는 애플리케이션 경우에는 로그를 이용하면 마지막 체크 포인트 이후로 한 모든 작업을 저장한 다음 시스템이 다운되었을 경우 기존 체크 포인트에 최근 수행된 작업을 다시 적용하는 방법을 쓸수 있을 것이다.

스프레드시트 경우,매번 데이터가 변경될때마다 디스크에 저장하지 않고, 특정 체크 포인트 이후의 모든 행동을 로그에 기록하는 시긍로 복구 시스템을 구축할 수 있을 것이다.

더 복잡한 애플리케이션에서는 이런 테크닉을 확장해서 일련의 작업들에 대해서 트랜잭션을 활용하여 모든 작업이 완벽하게 처리되도록 하거나, 그렇지 않으면 아무것도 처리되지 않게 롤백하도록 할 수 있다.


정리

커맨드 패턴

=> 요청내역을 객체로 캡슐화하여 클라이언트를 서로 다른 요청 내역에 따라 매개변수화 할 수 있다.

요청을 큐에 저장하거나 로그로 기록할 수도 있고 작업 취소 기능을 지원할 수도 있다.


요청을 하는 객체와 그 요청을 수행하는 객체를 분리시키고 싶다면 커맨드 패턴을 사용하라.


핵심정리

- 요청하는 객체와 그 요청을 수행하는 객체를 분리할 수 있다.

- 분리시키는 과정의 중심에는 커맨드 객체가 있으며, 이 객체가 행동이 들어 있는 리시버를 캡슐화한다.

- 인보커에서는 요청을 할때는 커맨드 객체의 execute() 메서드를 호출하면된다. execute() 메서드에서는 리시버에 있는 행동을 호출한다.

- 인보커는 커맨드를 통해서 매개변수화 될 수 있다. 이런 실행중에 동적으로 설정할 수도 있다.

- execute() 메서드가 마지막으로 호출되기 전의 기존 상태로 되돌리기 위한 작업 취소 메서드를 구현하면 커맨드 패턴을 통해서 작업 취소 기능을 지원할 수 도 있다.

- 매크로 커맨드는 커맨드를 확장해서 여러개의 커맨드를 한꺼번에 호출할 수 있게 해주는 간단한 방법이다. 매크로 커맨드에서도 어렵지 않게 작업취소 기능을 지원할 수 있다.

- 프로그래밍을 하다 보면 요청자체를 리시버한테 넘기지 않고 자기가 처리하는 '스마트' 커맨드 객체를 사용하는 경우도 종종있다.

- 커맨드 패턴을 활용하여 로그 및 트랜잭션 시스템을 구현하는 것도 가능하다.


출처 - Head First Design Patterns 저자- 에릭 프리먼, 엘리자베스 프리먼, 케이시 시에라, 버트 베이츠


반응형

'JAVA > Design Patterns' 카테고리의 다른 글

8.템플릿 메서드 패턴  (0) 2018.12.10
7.어댑터 패턴 / 퍼사드 패턴  (0) 2018.12.10
5. 싱글톤 패턴  (0) 2018.12.06
4. 팩토리 메서드 패턴 / 추상 팩토리 패턴  (0) 2018.12.05
2.옵저버 패턴  (0) 2018.12.04