Design Pattern | Command Pattern (커맨드 패턴)

Command 패턴이란?

  • Command라는 영어 단어는 명령이라는 의미이다.
  • Command 패턴은 명령을 나타내는 클래스의 인스턴스를 하나로 표현하는 방법이다.
  • 명령의 이력을 관리하고 싶을 때는, 그 인스턴스의 집합을 관리하면 된다. 명령 모음을 저장하면 동일한 명령을 실행하거나 여러 명령을 함께 새 명령으로 재사용할 수 있다.
  • GoF의 디자인 패턴에서는 행위에 대한 디자인 패턴 으로 분류된다.

Command 패턴 예제 프로그램

간단한 그림 그리기 프로그램이다.

Class Diagram
Command Pattern Class Diagram

1. Command 인터페이스

명령을 표현하는 인터페이스이다.

Command.java

package com.devkuma.designpattern.behavioral.command.command;

public interface Command {
    void execute();
}

2. MacroCommand 클래스

“여러 명령을 정리한 명령"을 표현하는 클래스이다.

MacroCommand.java

package com.devkuma.designpattern.behavioral.command.command;

import java.util.Iterator;
import java.util.Stack;

public class MacroCommand implements Command {

    // 명령 모음
    private Stack commands = new Stack();

    public void execute() {
        Iterator it = commands.iterator();
        while (it.hasNext()) {
            ((Command) it.next()).execute();
        }
    }

    // 추가
    public void append(Command cmd) {
        if (cmd != this) {
            commands.push(cmd);
        }
    }

    // 마지막 명령 삭제
    public void undo() {
        if (!commands.empty()) {
            commands.pop();
        }
    }

    // 전부 삭제
    public void clear() {
        commands.clear();
    }
}

3. Drawable 인터페이스

“그리기 대상"을 표현하는 인터페이스이다.

Drawable.java

package com.devkuma.designpattern.behavioral.command.drawer;

public interface Drawable {
    void draw(int x, int y);
}

4. DrawCanvas 클래스

“그리기 대상"을 구현한 클래스이다.

DrawCanvas.java

package com.devkuma.designpattern.behavioral.command.drawer;

import com.devkuma.designpattern.behavioral.command.command.MacroCommand;

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;

public class DrawCanvas extends Canvas implements Drawable {

    // 그리기 색상
    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();
    }

    // 그리기
    public void draw(int x, int y) {
        Graphics g = getGraphics();
        g.setColor(color);
        g.fillOval(x - radius, y - radius, radius * 2, radius * 2);
    }
}

5. DrawCommand 클래스

“점의 그리기 명령"을 표현하는 클래스이다.

DrawCommand.java

package com.devkuma.designpattern.behavioral.command.drawer;

import com.devkuma.designpattern.behavioral.command.command.Command;

import java.awt.Point;

public class DrawCommand implements Command {

    // 그리기 대상
    protected Drawable drawable;
    // 그리기 위치
    private Point position;

    public DrawCommand(Drawable drawable, Point position) {
        this.drawable = drawable;
        this.position = position;
    }

    public void execute() {
        drawable.draw(position.x, position.y);
    }
}

6. Main 클래스

메인 처리를 실행하는 클래스이다.

Main.java

package com.devkuma.designpattern.behavioral.command;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;

import com.devkuma.designpattern.behavioral.command.command.Command;
import com.devkuma.designpattern.behavioral.command.command.MacroCommand;
import com.devkuma.designpattern.behavioral.command.drawer.DrawCanvas;
import com.devkuma.designpattern.behavioral.command.drawer.DrawCommand;


public class Main extends JFrame implements ActionListener, MouseMotionListener, WindowListener {

    // 그리기 이력
    private MacroCommand history = new MacroCommand();
    // 그리기 영역
    private DrawCanvas canvas = new DrawCanvas(400, 400, history);
    // 실행 취소 버튼
    private JButton undoButton = new JButton("undo");
    // 지우기 버튼
    private JButton clearButton = new JButton("clear");

    public Main(String title) {
        super(title);

        this.addWindowListener(this);
        canvas.addMouseMotionListener(this);
        undoButton.addActionListener(this);
        clearButton.addActionListener(this);

        Box buttonBox = new Box(BoxLayout.X_AXIS);
        buttonBox.add(undoButton);
        buttonBox.add(clearButton);
        Box mainBox = new Box(BoxLayout.Y_AXIS);
        mainBox.add(buttonBox);
        mainBox.add(canvas);
        getContentPane().add(mainBox);

        pack();
        setVisible(true);
    }

    // ActionListener 용
    public void actionPerformed(ActionEvent e) {
        Object source = e.getSource();
        if (source == undoButton) {
            history.undo();
            canvas.repaint();
        } else if (source == clearButton) {
            history.clear();
            canvas.repaint();
        }
    }

    // MouseMotionListener用
    public void mouseMoved(MouseEvent e) {
    }

    public void mouseDragged(MouseEvent e) {
        Command cmd = new DrawCommand(canvas, e.getPoint());
        history.append(cmd);
        cmd.execute();
    }

    // WindowListener 용
    public void windowClosing(WindowEvent e) {
    }

    public void windowActivated(WindowEvent e) {
    }

    public void windowClosed(WindowEvent e) {
    }

    public void windowDeactivated(WindowEvent e) {
    }

    public void windowDeiconified(WindowEvent e) {
    }

    public void windowIconified(WindowEvent e) {
    }

    public void windowOpened(WindowEvent e) {
    }

    public static void main(String[] args) {
        new Main("Command Pattern Sample");
    }
}

7. 실행 결과

Command Pattern Result

Command 패턴의 장점

“명령"을 오브젝트로서 표현하는 것으로 명령의 이력을 취하거나 명령의 재실행을 실시할 수가 있다.
또, 새로운 “명령"을 추가하고 싶은 경우는 Command 인터페이스를 구현한 클래스를 작성하면 되는 것만이므로, 기능 확장이 실시하기 쉬워진다.