Posted at

デザインパターン勉強会 第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
命令の役。命令のインターフェース(API)を定義する

ConcreateCommand
具体的命令の役。Commandのインターフェースを実際に実装する

Receiver
受信者の役。命令の受け取り手となる

Client
依頼者の役。具体的命令を生成し、命令の受け取り手を割り当てる

Invoker
起動者の役。Commandで定義されているインターフェースを呼び出し、命令の実行を開始する



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

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



各クラスの役割

名前
役割

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




プログラム実行結果



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

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

・Compositeパターン

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

・Mementoパターン

Commandの履歴を保持するために、使用される場合がある。

・Prototypeパターン

発生した命令を複製するために、使用される場合がある。



考察

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