はじめに
本エントリーは某社内で実施するデザインパターン勉強会向けの資料となります。
本エントリーで書籍「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メソッドのみの実装を行いたいため、インターフェースを作成します。
public interface ICommand
{
void Execute();
}
MacroCommandクラス
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クラス
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インターフェース
interface IDrawable
{
void Draw(int x, int y);
}
DrawCanvasクラス
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クラス
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パターンを組み合わせて使用することで、命令というオブジェクトを管理しやすくなる場合があります。