Help us understand the problem. What is going on with this article?

デザインパターン勉強会 第22回:Commandパターン

More than 1 year has passed since last update.

はじめに

本エントリーは某社内で実施するデザインパターン勉強会向けの資料となります。
本エントリーで書籍「Java言語で学ぶデザインパターン入門」をベースに学習を進めますが、サンプルコードはC#に置き換えて解説します。

第1回:Iteratorパターン
第2回:Adapterパターン
第3回:Template Methodパターン
第4回:Factory Methodパターン
第5回:Singletonパターン
第6回:Prototypeパターン
第7回:Builderパターン
第8回:Abstract Factoryパターン
第9回:Bridgeパターン
第10回:Strategyパターン 
第11回:Compositeパターン
第12回:Decoratorパターン
第13回:Visitorパターン
第14回:Chain of Responsibilityパターン
第15回:Facadeパターン
第16回:Mediatorパターン
第17回:Observerパターン
第18回:Mementoパターン
第19回:Stateパターン
第20回:Flyweightパターン
第21回:Proxyパターン


Commandパターンとは

 Commandは「命令」という意味で、「この仕事を行いなさい」という「命令」をクラスに表したデザインパターンです。
命令を1つの「もの」として表現することにより、命令の集まりから履歴管理や複数の命令をまとめて新しい命令を作成することが可能になります。
 また、Commandは「イベント駆動型プログラミング」で使われるEventと呼ばれる場合もあります。GUIに関わるプログラミングではEventがよく登場しており、マウスのクリック、キーの操作といったイベントが起きた先に、そのイベントを「もの」にしておき、発生した順に処理していくという形で使われています。

Command.png

名前 役割
Command 命令の役。命令のインターフェース(API)を定義する
ConcreateCommand 具体的命令の役。Commandのインターフェースを実際に実装する
Receiver 受信者の役。命令の受け取り手となる
Client 依頼者の役。具体的命令を生成し、命令の受け取り手を割り当てる
Invoker 起動者の役。Commandで定義されているインターフェースを呼び出し、命令の実行を開始する

サンプルプログラムのクラス図

Commandパターンを用いた、お絵かきプログラムを作成します。

CommandSample.png


各クラスの役割

名前 役割
ICommand 「命令」を表現するインターフェース
MacroCommand 「複数の命令をまとめた命令」を表現するクラス
DrawCommand 「点の描画命令」を表現するクラス
IDrawable 「描画対象」を表現するインターフェース
DrawCanvas 「描画対象」を実装したクラス
MainForm 動作確認用クラス

ICommandインターフェース

ICommandインターフェースは、System.Windows.Input内に存在します。
今回はExecuteメソッドのみの実装を行いたいため、インターフェースを作成します。

ICommand.cs
public interface ICommand
{
    void Execute();
}

MacroCommandクラス

MacroCommand.cs
public class MacroCommand : ICommand
{
    private Stack<ICommand> commands = new Stack<ICommand>();

    // 実行
    public void Execute()
    {
        if (commands.Count > 0)
        {
            IEnumerator enumerator = commands.GetEnumerator();
            while (enumerator.MoveNext())
            {
                ICommand obj =(ICommand)enumerator.Current;
                obj.Execute();                
            }
        }
    }

    // 追加
    public virtual void Apppend(ICommand addCommand)
    {
        if (addCommand != this) commands.Push(addCommand);
    }

    // 削除
    public void Undo()
    {
        if (commands.Count > 0) commands.Pop();
    }

    // 全件削除
    public void Clear() => commands.Clear();
}

DrawCommandクラス

DrawCommand.cs
class DrawCommand : ICommand
{
    //描画対象
    protected IDrawable drawable;

    //描画位置
    private Point point;

    //コンストラクタ
    public DrawCommand(IDrawable drawable, Point point)
    {
        this.drawable = drawable;
        this.point = point;
    }

    //実行
    public void Execute()
    {
        drawable.Draw(point.X, point.Y);
    }
}

IDrawableインターフェース

IDrawable.cs
interface IDrawable
{
    void Draw(int x, int y);
}

DrawCanvasクラス

DrawCanvas.cs
public partial class DrawCanvas : UserControl, IDrawable
{
    public DrawCanvas()
    {
        InitializeComponent();
    }

    //描画色
    private Color color = Color.Black;
    //半径
    private int radius = 6;
    //履歴
    private MacroCommand history = null;

    //履歴セット(本来はコンストラクタ、今回はメソッドを用意)
    public void SetHistory(MacroCommand history) => this.history = history;

    //描画
    public void Draw(int x, int y)
    {
        using (Graphics g = CreateGraphics())
        using (Brush brush = new SolidBrush(color))
        {
            Rectangle rectangle = new Rectangle(x, y, radius, radius);
            g.FillEllipse(brush, rectangle);
        }
    }

    //再描画
    private void DrawCanvas_Paint(object sender, PaintEventArgs e)
    {
        if (history != null)
        {
            history.Execute();
        }
    }
}

MainFormクラス

MainForm.cs
public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();
        drawCanvas1.SetHistory(history);
    }

    //描画履歴
    private MacroCommand history = new MacroCommand();

    bool drag = false;

    //マウスが動いている時
    private void drawCanvas1_MouseMove(object sender, MouseEventArgs e)
    {
        if (drag)
        {
            ICommand command = new DrawCommand(drawCanvas1, e.Location);
            history.Apppend(command);
            command.Execute();
        }
    }

    //Clearボタン押下
    private void ClearButton_Click(object sender, EventArgs e)
    {
        history.Clear();
        drawCanvas1.Invalidate();
    }

    //Undoボタン押下
    private void Undo_Click(object sender, EventArgs e)
    {
        history.Undo();
        drawCanvas1.Invalidate();
    }

    private void drawCanvas1_MouseDown(object sender, MouseEventArgs e)
    {
        drag = true;
    }

    private void drawCanvas1_MouseUp(object sender, MouseEventArgs e)
    {
        drag = false;
    }
}

プログラム実行結果

command.PNG


関連するデザインパターン

今回のサンプルプログラムでは使用していないが、関連しているパターンがある。

・Compositeパターン
マクロコマンド(具体的命令役)を実現するために、使用される場合がある。

・Mementoパターン
Commandの履歴を保持するために、使用される場合がある。

・Prototypeパターン
発生した命令を複製するために、使用される場合がある。


考察

 「命令」をオブジェクトとして表現するCommandパターンを用いることで、命令の履歴管理、再実行、複製などを行うことができるようになります。これまでに学習したCompositeパターン、Mementoパターン、Prototypeパターンを組み合わせて使用することで、命令というオブジェクトを管理しやすくなる場合があります。

skyc_lin
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away