command pattern
Command 패턴-명령을 클래스로 만든다
클래스가 일을 수행할 때 자신의 클래스나 다른 클래스의 메소드를 호출한다. 메소드를 호출한 결과는 객체 상태로 반영이 되지만, 일을 수행한 이력은 어디에도 남지 않는다. 만약 실행하고 싶은 일을 메소드를 호출하는 동적인 처리로서 표현하는 것이 아니라, 이 일을 실행하시오! 라는 명령을 나타내는 클래스의 인스턴스의 집합으로 관리하게 되면 똑같은 명령을 재실행할 수도 있고, 여러 개의 명령을 모아 새로운 명령으로도 재사용할 수 있다. 이와 같은 ‘명령’ 을 Command 패턴이라고 한다.
메소드(작업 요청)을 객체의 형태로 캡슐화 하는 것이 가장 큰 목적이다.
예) 간단한 그림 그리기 소프트, 마우스를 끌면 빨간 점이 연결되어 그림이 그려지고 clear 버튼을 누르면 점이 지워진다.
사용자가 마우스를 끌때마다 ‘이 위치에 점을 그려라’ 라는 명령이 DrawCommand 클래스의 인스턴스로 생성된다. 이 인스턴스를 저장했다가 필요에 따라서 재사용한다.
패키지 | 이름 | 해설 |
---|---|---|
command | Command | ‘명령’을 표현하는 인터페이스 |
command | MacroCommand | ‘여러 개의 명령을 모은 명령’을 나타내는 클래스 |
drawer | DrawCommand | ‘그림 그리기 명령’을 표현한 클래스 |
drawer | Drawable | ‘그리기 대상’을 표현한 인터페이스 |
drawer | DrawCanvas | ‘그리기 대상’을 구현한 인터페이스 |
Anonymous | Main | 동작 테스트용 클래스 |
Command 인터페이스
‘명령’을 표현하기 위한 인터페이스
Command 인터페이스는 execute 메소드를 가지며, 호출했을때 구체적으로 일어나는 일은 Command 인터페이스를 구현한 클래스가 결정한다.
public interface Command {
/**
* 실행한다.
*/
public abstract void execute();
}
MacroCommand 클래스
여러개의 명령을 하나로 모은 명령을 나타내는 클래스
Command 인터페이스를 구현하고 있다.
execute() : commands 필드에 보관되어 있는 인스턴스의 execute() 메소드를 실행
append() : MacroCommand 클래스에 새로운 Command를 추가
if문의 조건은 만약 자기 자신을 실수 add해버리면, execute 메소드는 영원히 끝나지 않기 때문에, 사용
(java.util.stack 클래스의 push 메소드는 인스턴스에 마지막 요소를 추가한다는 의미)
undo() : commands의 마지막 명령어를 사게하는 메소드, pop을 통해 요소를 꺼낸다음 인스턴스에서 제거
clear() : 모든 명령을 삭제하는 메소드
public class MacroCommand implements Command {
/**
* Command 인터페이스의 execute() 구현
*
* @param commands 다수의 command를 모아두기 위한 필드
*/
private Stack commands = new Stack();
public void execute() {
Iterator it = commands.iterator();
while(it.hasNext()) {
((Command)it.next()).execute();
}
}
/**
* MacroCommand 클래스에 새로운 Command를 추가 (Command를 구현한 클래스의 인스턴스 추가)
*/
public void append(Command cmd) {
if(cmd != this) {
commands.push(cmd);
}
}
/**
* commands의 최후의 명령을 삭제
*/
public void undo() {
if(!commands.empty()) {
commands.pop();
}
}
/**
* 모든 명령을 삭제
*/
public void clear(){
commands.clear();
}
}
Drawable 인터페이스
그림 그리기 대상을 표현하는 인터페이스
draw() : 그림을 그리는 메소드
public interface Drawable {
/**
* 그림 그린다.
*/
public abstract void draw(int x, int y);
}
DrawCanvas 클래스
Drawable 인터페이스를 구현하고 있는 클래스
history 필드 : 그림을 그리도록 하는 명령의 집합
DrawCanvas 생성자 : 폭, 높이와 그림 내용을 받아서 DrawCanvas인스턴스를 초기화한다.
paint 메소드 : DrawCanvas를 다시 그릴 필요가 생겼을때, history에 기록되어 있는 명령의 집합을 재실행 시킨다.
draw 메소드 : Drawable 인터페이스 구현을 위해 정의되어 있는 메소드로, 색을 지정하고 원을 표시한다.
public class DrawCanvas extends Canvas implements Drawable {
/**
* DrawCanvas 클래스의 생성자
*
* @param color 그림 그리는 색
* @param radius 그림 그리기를 할 점의 반경
* @param history 그림을 그리도록 하는 명령의 집합 필드
*/
private Color color = Color.red;
private int radius = 6;
private MacroCommand history;
public DrawCanvas(int width, int height, MacroCommand history) {
setSize(width, height);
setBackground(Color.white);
this.history = history;
}
/**
* 이력 전체를 다시 그리기
*/
public void paint(Graphics g) {
history.execute();
}
/**
* 그리기
*/
@Override
public void draw(int x, int y) {
Graphics g = getGraphics();
g.setColor(color);
g.fillOval(x - radius, y - radius, radius * 2, radius * 2);
}
}
DrawCommand 클래스
Command 인터페이스를 구현한 클래스로, 그림 그리기 명령을 표현한다.
DrawCommand 생성자 : Drawable 인터페이스를 구현한 클래스의 인스턴스와 Point 클래스의 인스턴스를 인수로 넘겨 필드에 대입한다. => 이 위치에 점을 그려라!! 라는 명령을 생성
execute() : drawable 필드의 draw 메소드를 호출함으로서, 명령을 실행한다.
public class DrawCommand implements Command {
/**
* DrawCommand 클래스의 생성자, 명령 생성
*
* @param drawable 그림 그리기를 실행할 대상을 보관하는 필드
* @param position 그림 그리기를 행할 위치를 나타내는 필드
*/
protected Drawable drawable;
private Point position;
public DrawCommand(Drawable drawable, Point position) {
this.drawable = drawable;
this.position = position;
}
/**
* 명령 실행
*/
@Override
public void execute() {
drawable.draw(position.x, position.y);
}
}
Main 클래스
예제 프로그램을 작동시키기 위한 클래스
history 필드 : 그림의 이력을 보관
canvas 필드 : 그림을 그리는 영역 , 초기 사이즈로 400*400을 제공
clearButton 필드 : 그린 점을 지우는 제거 버튼
main생성자: 마우스 클릭 등의 이벤트를 받아들이는 리스너를 설정, 그림 그리기할 컴포넌트를 배치하고 있다.
- 가로로 컴포넌트를 나열할 박스 buttonBox 생성
- buttonBox 위에 clearButton 생성
- 세로로 컴포넌트를 나열할 박스 mainBox 생성
- mainBox위에 buttonBox와 canvas 나열
- JFrame 위에 mainBox 나열 (getContentPane 컨테이너 이용)
actionPerformed() : ActionListener 인터페이스 구현하기 위한 것으로, clearButton이 눌러졌을때 그림 이력을 제거 후 다시 그리기 실행
mouseDragged() : MouseMotionListener 인터페이스를 구현하기 위한 것으로, 마우스를 끌었을때, 이 점을 그려라는 명령을 생성함
windowClosing() : WindowListener 인터페이스를 구현하기 위한 것으로, 종료 처리
public class HBNMain extends JFrame implements ActionListener, MouseMotionListener, WindowListener {
/**
* HBNMain 클래스의 생성자
*
* @param history 그림 그리기 이력
* @param canvas 그림 그리기 영역
* @param clearButton 제거 버튼
*/
private MacroCommand history = new MacroCommand();
private DrawCanvas canvas = new DrawCanvas(400, 400, history);
private JButton clearButton = new JButton("clear");
public HBNMain(String title) {
super(title);
this.addWindowListener(this);
canvas.addMouseMotionListener(this);
clearButton.addActionListener(this);
Box buttonBox = new Box(BoxLayout.X_AXIS);
buttonBox.add(clearButton);
Box mainBox = new Box(BoxLayout.Y_AXIS);
mainBox.add(buttonBox);
mainBox.add(canvas);
getContentPane().add(mainBox);
pack();
setVisible(true);
}
/**
* ActionListener 인터페이스 구현
*/
@Override
public void actionPerformed(ActionEvent e) {
if(e.getSource() == clearButton) {
history.clear();
canvas.repaint();
}
}
/**
* MouseMotionListener 인터페이스 구현
*/
@Override
public void mouseDragged(MouseEvent e) {
Command cmd = new DrawCommand(canvas, e.getPoint());
history.append(cmd); // 실행 이력에 추가
cmd.execute(); // 실행
}
@Override
public void mouseMoved(MouseEvent e) { }
@Override
public void windowOpened(WindowEvent e) { }
/**
* WindowListener 인터페이스 구현
*/
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
@Override
public void windowClosed(WindowEvent e) { }
@Override
public void windowIconified(WindowEvent e) { }
@Override
public void windowDeiconified(WindowEvent e) { }
@Override
public void windowActivated(WindowEvent e) { }
@Override
public void windowDeactivated(WindowEvent e) { }
/**
* Main 클래스의 인스턴스 생성 및 실행
*/
public static void main(String[] args) {
new HBNMain("Command Pattern Sample");
}
}
Command 패턴에 등장하는 역할
- Command(명령)의 역할
명령의 인터페이스(API)를 정의하는 역할
예) Command 인터페이스
- ConcreateCommand(구체적인 명령)의 역할
Command 인터페이스를 실제로 구현하고 있는 역할
예) MacroCommand 클래스, DrawCommand 클래스
- Receiver(수신자)의 역할
Command 명령을 실행할때 대상이 되는 역할, 명령을 받아들이는 사람!
예)DrawCanvas클래스
- Client(의뢰자) 의 역할
ConcreateCommand를 생성하고, Receiver를 할당하는 역할
예) Main클래스- 마우스 끌기에 맞춰 DrawCommand 인스턴스 생성 및 DrawCanvas의 인스턴스를 생성자에게 전달
- invoker(기동자)의 역할
명령을 처음 실행하는 역할, Command에서 정의되어 있는 인터페이스를 호출
예) Main 클래스, DrawCanvas 클래스 - Command 인터페이스의 execute를 호출
command pattern 활용
- 요청을 로그에 기록하기
어떤 애플리케이션에서 명령을 실행하면서 디스크에 실행 히스토리를 기록해 애플리케이션이 다운되면,
command 객체를 다시 로딩하고 execute()메소드를 자동으로 실행하면 애플리케이션이 다운되었을경우, 복구할 수 있다.
ex) DB의 commit, rollback연산
참고자료
- 커멘트 패턴(Command Pattern)
- Desion Pattern- 제6강: Command 패턴
- [Java 언어로 배우는 디자인 패턴 입문 , 영진닷컴]