デザインパターン ~Command~

設計

1. はじめに

GoFのデザインパターンにおける、Commandパターンについてまとめます。

2. Commandパターンとは

  • Commandという英単語は、命令という意味になります。
  • Commandパターンは、命令を表すクラスのインスタンスを、1つのものとして表現する方式です。
  • 命令の履歴を管理したいときは、そのインスタンスの集まりを管理すればいいことになります。命令の集まりを保存しておけば、同じ命令を実行したり、複数の命令をまとめて新しい命令として再利用したりできます。
  • GoFのデザインパターンでは、振る舞いに関するデザインパターンに分類されます。

3. サンプルクラス図

Command.PNG

4. サンプルプログラム

簡単なお絵かきプログラムです。

4-1. Commandインターフェース

命令を表現するインターフェースです。

package command;

public interface Command {
    public abstract void execute();
}

4-2. MacroCommandクラス

「複数の命令をまとめた命令」を表現するクラスです。

package 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();
    }
}

4-3. Drawableインターフェース

「描画対象」を表現するインターフェースです。

package drawer;

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

4-4. DrawCanvasクラス

「描画対象」を実装したクラスです。

package drawer;

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

import command.MacroCommand;

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);
    }
}

4-5. DrawCommandクラス

「点の描画命令」を表現するクラスです。

package drawer;

import java.awt.Point;

import command.Command;

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);
    }
}

4-6. Mainクラス

メイン処理を行うクラスです。

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 command.Command;
import command.MacroCommand;
import drawer.DrawCanvas;
import 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();
        show();
    }

    // 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");
    }
}

4-7. 実行結果

e.png

5. メリット

「命令」をオブジェクトとして表現することで、命令の履歴をとったり、命令の再実行を行ったりすることができます。 また、新しい「命令」を追加したい場合は、Commandインターフェースを実装したクラスを作成すればよいだけなので、機能拡張が行いやすくなります。

6. GitHub

7. デザインパターン一覧

8. 参考

今回の記事、及びサンプルプログラムは、以下の書籍を元に作成させて頂きました。

大変分かりやすく、勉強になりました。感謝申し上げます。 デザインパターンやサンプルプログラムについての説明が詳細に書かれていますので、是非書籍の方もご覧ください。