개발/Java

디자인 패턴 - Command Pattern

haloper 2016. 3. 29. 14:30

커맨드 패턴의 주요 목적은

작업을 요청하는 쪽과 작업을 처리하는 쪽을 분리하여

서로 의존관계가 없도록 하는 것 입니다.

(서로 상대방의 존재 여부를 알지도, 알 필요도 없습니다.)

또한, 커맨드 패턴 적용 시

연속된 작업의 이력 관리와

작업 취소 로직을 쉽게 적용할 수 있습니다.


샘플 코드를 작성하기에 앞서

기본적인 용어를 정리하도록 하겠습니다.

Invoker : 작업을 요청하는 객체

Receiver : 작업을 처리하는 객체

Command : 작업 클래스들이 구현해야 하는 인터페이스


간단하게 원과 네모를 그리는 로직으로 샘플을 구현해 보겠습니다.

x,y 좌표를 가진 원과 네모를 그리거나 지우는 작업을 합니다.

작업 이력을 stack에 넣어 보관하고,

필요시 undo 할 수도 있습니다.


우선 Command Interface를 작성하겠습니다.

* Command.java

public interface Command {

	public void execute();
	
	public void undo();
}


Command 인터페이스를 구현하는 Command 클래스들 입니다.

각 클래스의 인스턴스가 하나의 작업을 나타냅니다.

* Command Class

//DrawCircleCommand.java
public class DrawCircleCommand implements Command {
	
	private CircleReceiver circle;
	
	public DrawCircleCommand(CircleReceiver circle) {
		this.circle = circle;
	}

	@Override
	public void execute() {
		circle.Draw();
	}

	@Override
	public void undo() {
		circle.Erase();
	}

}

//EraseCircleCommand.java
public class EraseCircleCommand implements Command {
	
	private CircleReceiver circle;
	
	public EraseCircleCommand(CircleReceiver circle) {
		this.circle = circle;
	}

	@Override
	public void execute() {
		circle.Erase();
	}

	@Override
	public void undo() {
		circle.Draw();
	}

}

//DrawRectCommand.java
public class DrawRectCommand implements Command {
	
	private RectReceiver rect;
	
	public DrawRectCommand(RectReceiver rect) {
		this.rect = rect;
	}

	@Override
	public void execute() {
		rect.Draw();
	}

	@Override
	public void undo() {
		rect.Erase();
	}

}

//EraseRectCommand.java
public class EraseRectCommand implements Command {
	
	private RectReceiver rect;
	
	public EraseRectCommand(RectReceiver rect) {
		this.rect = rect;
	}

	@Override
	public void execute() {
		rect.Erase();
	}

	@Override
	public void undo() {
		rect.Draw();
	}

}


작업 내용을 알고 있는,

실제로 작업을 처리할 Receiver 클래스를 만들어 보겠습니다.

각 작업에 대한 실제 로직이 들어 있습니다.

* Receiver Class

//CircleReceiver.java
public class CircleReceiver {
	
	private int x,y;
	
	public CircleReceiver(int x, int y) {
		this.x = x;
		this.y= y;
	}
	
	public void Draw() {
		System.out.println(String.format("원을 그립니다. (%d,%d)", x, y));
	}
	
	public void Erase() {
		System.out.println(String.format("원을 지웁니다. (%d,%d)", x, y));
	}

}

//RectReceiver.java
public class RectReceiver {
	
	int x,y;
	
	public RectReceiver(int x, int y) {
		this.x = x;
		this.y= y;
	}
	
	public void Draw() {
		System.out.println(String.format("네모를 그립니다. (%d,%d)", x, y));
	}
	
	public void Erase() {
		System.out.println(String.format("네모를 지웁니다. (%d,%d)", x, y));
	}

}


작업을 처리할 모든 준비가 되었습니다.

이제, 작업을 요청하는 Invoker 클래스를 만들겠습니다.

* DrawingInvoker.java

public class DrawingInvoker {
	
	private Stack<Command> commandStack;
	
	public DrawingInvoker() {
		commandStack = new Stack<Command>();
	}
	
	public void execute(Command command) {
		commandStack.push(command);
		command.execute();
	}
	
	public void undo() {
		commandStack.pop().undo();
	}

}


마지막으로 위 패턴이 잘 동작하는지 확인할

Cilent Class 를 만들어서 확인해 보도록 하겠습니다.

* Client.java

public class Client {
	
	public static void main(String[] args) {
		
		DrawingInvoker drawing = new DrawingInvoker();
		
		//원을 그리고
		drawing.execute(new DrawCircleCommand(new CircleReceiver(3,5))); //원을 그립니다. (3,5)
		
		//네모를 그리고
		RectReceiver rect = new RectReceiver(12,3);
		drawing.execute(new DrawRectCommand(rect)); //네모를 그립니다. (12,3)
		
		//네모를 지웠다가 다시 그리고
		drawing.execute(new EraseRectCommand(rect)); //네모를 지웁니다. (12,3)
		drawing.execute(new DrawRectCommand(rect)); //네모를 그립니다. (12,3)
		
		//원을 그린다
		drawing.execute(new DrawCircleCommand(new CircleReceiver(1,1))); //원을 그립니다. (1,1)
		
		//잘못 그렸네.. 작업 취소
		drawing.undo(); //원을 지웁니다. (1,1)
		drawing.undo(); //네모를 지웁니다. (12,3)
		drawing.undo(); //네모를 그립니다. (12,3)
		//네모를 다시 그림
		drawing.execute(new DrawRectCommand(rect)); //네모를 그립니다. (12,3)
	}
}