Command (コマンド) パターン

あるオブジェクトに対しての要求をメソッドではなく、クラスで表現しておくデザインパターンです。
あれ?ちょっと下のサンプルってどこかで見た気が...。
そうです。Decolator パターンで実装していたものをちょっとだけ変えて実装しています。
# ネタ切れw


サンプルでは、『こんな外観のこのコントロールを作れ』という要求オブジェクトを
命令のクラスに渡して、その要求をかなえています。
サンプルの中で使用している、ColorDialogFontDialog についての詳細は、じゃんぬさんのページを参照して下さい。

このカテゴリーの先頭へ このページの先頭へ

■クラス図

ここで用いるサンプルのクラス図です。
準備中...

このカテゴリーの先頭へ このページの先頭へ

■サンプルの説明

サンプルの見た目1  サンプルの見た目2
BackColor・Font・ForeColor を任意で選択し、コンボボックスのコントロール名を選択します。
作成ボタンをクリックすると、コンボボックスで選択したコントロールの BackColor・Font・ForeColor が
選択した色やフォントであるコントロールが画面に生成されます。
サンプルのプロジェクトダウンロード

このカテゴリーの先頭へ このページの先頭へ

■コード

アプリケーションのエントリポイント StartUpForm です。

StartUpForm.vb
Public Class StartUpForm
  Inherits System.Windows.Forms.Form

#Region " Windows フォーム デザイナで生成されたコード "

  Public Sub New()
    MyBase.New()

    ' この呼び出しは Windows フォーム デザイナで必要です。
    InitializeComponent()

    ' InitializeComponent() 呼び出しの後に初期化を追加します。
    AddHandler Application.ThreadException, AddressOf CommonExceptionAndHandler.ExceptionHandler.MyHandler
  End Sub

  ' Form は、コンポーネント一覧に後処理を実行するために dispose をオーバーライドします。
  Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
    If disposing Then
      If Not (components Is Nothing) Then
        components.Dispose()
      End If
    End If
    MyBase.Dispose(disposing)
  End Sub

  ' Windows フォーム デザイナで必要です。
  Private components As System.ComponentModel.IContainer

  ' メモ : 以下のプロシージャは、Windows フォーム デザイナで必要です。
  'Windows フォーム デザイナを使って変更してください。  
  ' コード エディタを使って変更しないでください。
  Friend WithEvents Button3 As System.Windows.Forms.Button
  Friend WithEvents Label3 As System.Windows.Forms.Label
  Friend WithEvents Button4 As System.Windows.Forms.Button
  Friend WithEvents ComboBox1 As System.Windows.Forms.ComboBox
  Friend WithEvents Button2 As System.Windows.Forms.Button
  Friend WithEvents Label2 As System.Windows.Forms.Label
  Friend WithEvents Button1 As System.Windows.Forms.Button
  Friend WithEvents Label1 As System.Windows.Forms.Label
  Friend WithEvents GroupBox1 As System.Windows.Forms.GroupBox
  <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
    Me.Button3 = New System.Windows.Forms.Button
    Me.Label3 = New System.Windows.Forms.Label
    Me.Button4 = New System.Windows.Forms.Button
    Me.ComboBox1 = New System.Windows.Forms.ComboBox
    Me.Button2 = New System.Windows.Forms.Button
    Me.Label2 = New System.Windows.Forms.Label
    Me.Button1 = New System.Windows.Forms.Button
    Me.Label1 = New System.Windows.Forms.Label
    Me.GroupBox1 = New System.Windows.Forms.GroupBox
    Me.SuspendLayout()
    '
    'Button3
    '
    Me.Button3.Location = New System.Drawing.Point(160, 56)
    Me.Button3.Name = "Button3"
    Me.Button3.TabIndex = 20
    Me.Button3.Text = "Button3"
    '
    'Label3
    '
    Me.Label3.Location = New System.Drawing.Point(56, 56)
    Me.Label3.Name = "Label3"
    Me.Label3.TabIndex = 19
    Me.Label3.Text = "Label3"
    '
    'Button4
    '
    Me.Button4.Location = New System.Drawing.Point(136, 96)
    Me.Button4.Name = "Button4"
    Me.Button4.TabIndex = 18
    Me.Button4.Text = "Button4"
    '
    'ComboBox1
    '
    Me.ComboBox1.Location = New System.Drawing.Point(8, 96)
    Me.ComboBox1.Name = "ComboBox1"
    Me.ComboBox1.Size = New System.Drawing.Size(121, 20)
    Me.ComboBox1.TabIndex = 17
    Me.ComboBox1.Text = "ComboBox1"
    '
    'Button2
    '
    Me.Button2.Location = New System.Drawing.Point(160, 32)
    Me.Button2.Name = "Button2"
    Me.Button2.TabIndex = 16
    Me.Button2.Text = "Button2"
    '
    'Label2
    '
    Me.Label2.Location = New System.Drawing.Point(56, 32)
    Me.Label2.Name = "Label2"
    Me.Label2.TabIndex = 15
    Me.Label2.Text = "Label2"
    '
    'Button1
    '
    Me.Button1.Location = New System.Drawing.Point(160, 8)
    Me.Button1.Name = "Button1"
    Me.Button1.TabIndex = 14
    Me.Button1.Text = "Button1"
    '
    'Label1
    '
    Me.Label1.Location = New System.Drawing.Point(56, 8)
    Me.Label1.Name = "Label1"
    Me.Label1.TabIndex = 13
    Me.Label1.Text = "Label1"
    '
    'GroupBox1
    '
    Me.GroupBox1.Location = New System.Drawing.Point(8, 128)
    Me.GroupBox1.Name = "GroupBox1"
    Me.GroupBox1.Size = New System.Drawing.Size(280, 128)
    Me.GroupBox1.TabIndex = 21
    Me.GroupBox1.TabStop = False
    Me.GroupBox1.Text = "GroupBox1"
    '
    'StartUpForm
    '
    Me.AutoScaleBaseSize = New System.Drawing.Size(5, 12)
    Me.ClientSize = New System.Drawing.Size(292, 266)
    Me.Controls.Add(Me.GroupBox1)
    Me.Controls.Add(Me.Button3)
    Me.Controls.Add(Me.Label3)
    Me.Controls.Add(Me.Button4)
    Me.Controls.Add(Me.ComboBox1)
    Me.Controls.Add(Me.Button2)
    Me.Controls.Add(Me.Label2)
    Me.Controls.Add(Me.Button1)
    Me.Controls.Add(Me.Label1)
    Me.Name = "StartUpForm"
    Me.Text = "Form1"
    Me.ResumeLayout(False)

  End Sub

#End Region

  '生成するコントロール
  Private Const CTRL_BUTTON As String = "ボタン"
  Private Const CTRL_TEXT As String = "テキストボックス"
  Private Const CTRL_LABEL As String = "ラベル"

  '画面がロードされた時のイベント
  Private Sub StartUpForm_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
    Me.Text = "Command"

    Me.Label1.Text = "BackColor"
    Me.Label1.BorderStyle = BorderStyle.FixedSingle
    Me.Button1.Text = "参照"

    Me.Label2.Text = "Font"
    Me.Label2.BorderStyle = BorderStyle.FixedSingle
    Me.Button2.Text = "参照"

    Me.Label3.Text = "ForeColor"
    Me.Label3.BorderStyle = BorderStyle.FixedSingle
    Me.Button3.Text = "参照"

    Me.ComboBox1.Items.Clear()
    Me.ComboBox1.DropDownStyle = ComboBoxStyle.DropDownList
    Me.ComboBox1.Items.Add(Me.CTRL_BUTTON)
    Me.ComboBox1.Items.Add(Me.CTRL_TEXT)
    Me.ComboBox1.Items.Add(Me.CTRL_LABEL)
    Me.ComboBox1.SelectedIndex = 0

    Me.Button4.Text = "生成"

    Me.GroupBox1.Text = ""
  End Sub

  '色選択ダイアログの表示
  Private Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
                Handles Button1.Click, Button3.Click

    Dim targetlabel As Label
    If sender Is Me.Button1 Then
      targetlabel = Me.Label1
    Else
      targetlabel = Me.Label3
    End If

    Dim colorDlg As ColorDialog = New ColorDialog
    Try
      colorDlg.Color = targetlabel.BackColor
      colorDlg.FullOpen = True
      colorDlg.AnyColor = True
      colorDlg.SolidColorOnly = False
      colorDlg.ShowHelp = True

      If colorDlg.ShowDialog(Me) = DialogResult.OK Then
        targetlabel.BackColor = colorDlg.Color
      End If
    Finally
      If Not colorDlg Is Nothing Then colorDlg.Dispose()
    End Try
  End Sub

  'フォント選択ダイアログの表示
  Private Sub Button2_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button2.Click

    Dim fontDlg As FontDialog = New FontDialog
    Try
      fontDlg.FontMustExist = True
      fontDlg.ShowHelp = True

      ' ダイアログを表示し、戻り値が [OK] の場合は選択したフォントを Label2 に適用する
      If fontDlg.ShowDialog() = DialogResult.OK Then
        Me.Label2.Font = fontDlg.Font
      End If
    Finally
      If Not fontDlg Is Nothing Then fontDlg.Dispose()
    End Try
  End Sub

  '生成ボタンをクリックした時のイベント
  Private Sub Button4_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button4.Click
    Me.GroupBox1.Controls.Clear()

    Dim createdtControl As Control
    Dim cmd As AbstractCommand
    Dim dmd As Demand = New Demand(Me.Label1.BackColor, _
                     Me.Label2.Font, _
                     Me.Label3.BackColor)

    If Me.ComboBox1.Text = Me.CTRL_BUTTON Then
      'Button
      cmd = New ButtonCreateCmmand
    ElseIf Me.ComboBox1.Text = Me.CTRL_TEXT Then
      'TextBox
      cmd = New TextBoxCreateCommand
    Else
      'Label
      cmd = New LabelCreateCommand
    End If

    '要求をセット
    cmd.SetDemand(dmd)
    'コントロール作成
    createdtControl = cmd.Create()

    createdtControl.Text = cmd.GetType().Name '現在のインスタンスの名前を表示
    createdtControl.Location = New Point(10, 10)
    createdtControl.Size = New Size(200, 20)
    'コントロールをGroupBoxにAdd
    Me.GroupBox1.Controls.Add(createdtControl)

  End Sub
End Class
このカテゴリーの先頭へ このページの先頭へ

こんな外観で作って欲しい、という要求オブジェクトです。

Demand.vb
'見た目の要求
Public Class Demand

  '背景色
  Private m_backColor As Color
  'フォント
  Private m_font As Font
  '前面色
  Private m_foreColor As Color

  'コンストラクタ
  Public Sub New(ByVal bkColor As Color, _
           ByVal fnt As Font, _
           ByVal frColor As Color)
    Me.m_backColor = bkColor
    Me.m_font = fnt
    Me.m_foreColor = frColor
  End Sub

  '背景色アクセサ
  Public ReadOnly Property BackColor() As Color
    Get
      Return Me.m_backColor
    End Get
  End Property

  'フォントアクセサ
  Public ReadOnly Property GetFont() As Font
    Get
      Return Me.m_font
    End Get
  End Property

  '前面色アクセサ
  Public ReadOnly Property ForeColor() As Color
    Get
      Return Me.m_foreColor
    End Get
  End Property
End Class
このカテゴリーの先頭へ このページの先頭へ

要求された外観のコントロールを作成する命令の抽象親クラスです。

AbstractCommand.vb
'要求された見た目のコントロールを作成する命令
Public MustInherit Class AbstractCommand

  '要求
  Protected m_Demand As Demand

  '要求を設定する
  Public Sub SetDemand(ByVal dmd As Demand)
    Me.m_Demand = dmd
  End Sub

  'コントロールを作成するメソッド定義
  Public MustOverride Function Create() As Control
End Class
このカテゴリーの先頭へ このページの先頭へ

要求具体的な命令であり、指示された外観の Button コントロールを作成するクラスです。
AbstractCommand を継承しています。

ButtonCreateCmmand.vb
Public Class ButtonCreateCmmand
  Inherits AbstractCommand

  'コントロールを作成するメソッド実装
  Public Overrides Function Create() As System.Windows.Forms.Control

    Dim bt As Control = New Button
    bt.BackColor = MyBase.m_Demand.BackColor
    bt.Font = MyBase.m_Demand.GetFont
    bt.ForeColor = MyBase.m_Demand.ForeColor

    Return bt
  End Function
End Class
このカテゴリーの先頭へ このページの先頭へ

要求具体的な命令であり、指示された外観の Label コントロールを作成するクラスです。
AbstractCommand を継承しています。

LabelCreateCommand.vb
Public Class LabelCreateCommand
  Inherits AbstractCommand

  'コントロールを作成するメソッド実装
  Public Overrides Function Create() As System.Windows.Forms.Control

    Dim lb As Control = New Label
    lb.BackColor = MyBase.m_Demand.BackColor
    lb.Font = MyBase.m_Demand.GetFont
    lb.ForeColor = MyBase.m_Demand.ForeColor

    Return lb
  End Function

End Class
このカテゴリーの先頭へ このページの先頭へ

要求具体的な命令であり、指示された外観の TextBox コントロールを作成するクラスです。
AbstractCommand を継承しています。

TextBoxCreateCommand.vb
Public Class TextBoxCreateCommand
  Inherits AbstractCommand

  'コントロールを作成するメソッド実装
  Public Overrides Function Create() As System.Windows.Forms.Control

    Dim tx As Control = New TextBox
    tx.BackColor = MyBase.m_Demand.BackColor
    tx.Font = MyBase.m_Demand.GetFont
    tx.ForeColor = MyBase.m_Demand.ForeColor

    Return tx
  End Function

End Class
このカテゴリーの先頭へ このページの先頭へ

■ひとこと

Decolator と似たような事をやっているのに、
中身の実装が殆ど異なる事を感じていただけたでしょうか。
同じ様な事をやりたい場合でも、色々と実装方法があるのを知るのはおもしろいですね。


Command パターンを適用した場合、命令が増えても柔軟に対応する事ができます。
サンプルの場合、RichTextBox に適用したい時は、RitchTextBox 用のコマンドクラスを作成してやればよいのです。
ただし、Command パターンが最も威力を発揮するのは、要求を処理するに当たって、
他のクラスと依存関係が殆ど無い場合であり、他クラスとの依存関係が強い場合は依存するクラスの全てに
変更の影響が波紋する恐れがあります。
その為、このデザインパターンを適用するには、要求の内容をよく吟味してから行った方がよさそうです。

このカテゴリーの先頭へ このページの先頭へ