Decorator (デコレータ) パターン
Decorator → Decoration と聞くと思い浮かぶのは...?きれいに飾り付けられたケーキですね。
飾り付けられるもの(スポンジ)と、追加してつけるもの(生クリームや苺などの装飾)を同じように扱う事によって、
機能の追加を柔軟に行うデザインパターンです。
サンプルでは、Button や TextBox や Label を、上記例でいうとスポンジに見立て、
ユーザーが選んだ装飾を動的に施しています。
サンプルの中で使用している、ColorDialog や FontDialog についての詳細は、じゃんぬさんのページを参照して下さい。
■クラス図
ここで用いるサンプルのクラス図です。
準備中...
■サンプルの説明

BackColor・Font・ForeColor を任意で選択し、コンボボックスのコントロール名を選択します。
実行ボタンをクリックすると、コンボボックスで選択したコントロールの BackColor・Font・ForeColor が
選択した色やフォントに変更されます。
サンプルのプロジェクトダウンロード
■コード
アプリケーションのエントリポイント StartUpForm です。
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 Label1 As System.Windows.Forms.Label Friend WithEvents Button1 As System.Windows.Forms.Button Friend WithEvents Button2 As System.Windows.Forms.Button Friend WithEvents Label2 As System.Windows.Forms.Label Friend WithEvents GroupBox1 As System.Windows.Forms.GroupBox Friend WithEvents TextBox1 As System.Windows.Forms.TextBox Friend WithEvents Button4 As System.Windows.Forms.Button Friend WithEvents Label4 As System.Windows.Forms.Label Friend WithEvents Button5 As System.Windows.Forms.Button Friend WithEvents Button3 As System.Windows.Forms.Button Friend WithEvents Label3 As System.Windows.Forms.Label Friend WithEvents ComboBox1 As System.Windows.Forms.ComboBox <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent() Me.Label1 = New System.Windows.Forms.Label Me.Button1 = New System.Windows.Forms.Button Me.Button2 = New System.Windows.Forms.Button Me.Label2 = New System.Windows.Forms.Label Me.ComboBox1 = New System.Windows.Forms.ComboBox Me.Button4 = New System.Windows.Forms.Button Me.GroupBox1 = New System.Windows.Forms.GroupBox Me.Label4 = New System.Windows.Forms.Label Me.TextBox1 = New System.Windows.Forms.TextBox Me.Button5 = New System.Windows.Forms.Button Me.Button3 = New System.Windows.Forms.Button Me.Label3 = New System.Windows.Forms.Label Me.GroupBox1.SuspendLayout() Me.SuspendLayout() ' 'Label1 ' Me.Label1.Location = New System.Drawing.Point(56, 8) Me.Label1.Name = "Label1" Me.Label1.TabIndex = 1 Me.Label1.Text = "Label1" ' 'Button1 ' Me.Button1.Location = New System.Drawing.Point(160, 8) Me.Button1.Name = "Button1" Me.Button1.TabIndex = 2 Me.Button1.Text = "Button1" ' 'Button2 ' Me.Button2.Location = New System.Drawing.Point(160, 32) Me.Button2.Name = "Button2" Me.Button2.TabIndex = 7 Me.Button2.Text = "Button2" ' 'Label2 ' Me.Label2.Location = New System.Drawing.Point(56, 32) Me.Label2.Name = "Label2" Me.Label2.TabIndex = 6 Me.Label2.Text = "Label2" ' '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 = 8 Me.ComboBox1.Text = "ComboBox1" ' 'Button4 ' Me.Button4.Location = New System.Drawing.Point(136, 96) Me.Button4.Name = "Button4" Me.Button4.TabIndex = 9 Me.Button4.Text = "Button4" ' 'GroupBox1 ' Me.GroupBox1.Controls.Add(Me.Label4) Me.GroupBox1.Controls.Add(Me.TextBox1) Me.GroupBox1.Controls.Add(Me.Button5) 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 = 10 Me.GroupBox1.TabStop = False Me.GroupBox1.Text = "GroupBox1" ' 'Label4 ' Me.Label4.Location = New System.Drawing.Point(152, 32) Me.Label4.Name = "Label4" Me.Label4.TabIndex = 2 Me.Label4.Text = "Label4" ' 'TextBox1 ' Me.TextBox1.Location = New System.Drawing.Point(80, 80) Me.TextBox1.Name = "TextBox1" Me.TextBox1.TabIndex = 1 Me.TextBox1.Text = "TextBox1" ' 'Button5 ' Me.Button5.Location = New System.Drawing.Point(24, 32) Me.Button5.Name = "Button5" Me.Button5.TabIndex = 0 Me.Button5.Text = "Button5" ' 'Button3 ' Me.Button3.Location = New System.Drawing.Point(160, 56) Me.Button3.Name = "Button3" Me.Button3.TabIndex = 12 Me.Button3.Text = "Button3" ' 'Label3 ' Me.Label3.Location = New System.Drawing.Point(56, 56) Me.Label3.Name = "Label3" Me.Label3.TabIndex = 11 Me.Label3.Text = "Label3" ' 'StartUpForm ' Me.AutoScaleBaseSize = New System.Drawing.Size(5, 12) Me.ClientSize = New System.Drawing.Size(292, 266) Me.Controls.Add(Me.Button3) Me.Controls.Add(Me.Label3) Me.Controls.Add(Me.GroupBox1) 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.GroupBox1.ResumeLayout(False) 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 = "Decolator" 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 cd.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 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 Dim targetControl As Control Dim decolator As IComponent If Me.ComboBox1.Text = Me.CTRL_BUTTON Then targetControl = Me.Button5 ElseIf Me.ComboBox1.Text = Me.CTRL_TEXT Then targetControl = Me.TextBox1 Else targetControl = Me.Label4 End If decolator = New BackColorDecolator( _ New FontDecolator( _ New ForeColorDecolater( _ New PureControl(targetControl), _ Me.Label3.BackColor), _ Me.Label2.Font), _ Me.Label1.BackColor) decolator.Decolate() End Sub End Class
飾られるモノと飾るモノを同一視するためのインターフェースです。
'飾られるモノと飾るモノを同一視する為のインターフェース Public Interface IComponent '背景色 Property BackColor() As Color 'フォント Property Font() As Font '前面色 Property ForeColor() As Color '装飾を行うメソッド定義 Sub Decolate() End Interface
実際に飾られるモノを保持するクラスです。
IComponent を実装しています。
Public Class PureControl Implements IComponent '実際に飾り付けを受けるコントロールの参照 Private m_control As Control 'コンストラクタ Public Sub New(ByVal ctrl As Control) Me.m_control = ctrl End Sub '背景色(実際に飾り付けを受けるコントロールの背景色) Public Property BackColor() As System.Drawing.Color Implements IComponent.BackColor Get Return Me.m_control.BackColor End Get Set(ByVal Value As System.Drawing.Color) Me.m_control.BackColor = Value End Set End Property 'フォント(実際に飾り付けを受けるコントロールのフォント) Public Property Font() As System.Drawing.Font Implements IComponent.Font Get Return Me.m_control.Font End Get Set(ByVal Value As System.Drawing.Font) Me.m_control.Font = Value End Set End Property '前面色(実際に飾り付けを受けるコントロールの前面色) Public Property ForeColor() As System.Drawing.Color Implements IComponent.ForeColor Get Return Me.m_control.ForeColor End Get Set(ByVal Value As System.Drawing.Color) Me.m_control.ForeColor = Value End Set End Property '装飾を行うメソッド実装 Public Sub Decolate() Implements IComponent.Decolate Me.m_control.Text = Me.m_control.GetType().Name End Sub End Class
飾り付けを行うクラスの基底の抽象クラスです。
IComponent の Decolate メソッド以外を実装しています。
'飾り付けを行うクラスの抽象 基底クラス Public MustInherit Class AbstractDecolator Implements IComponent Protected m_component As IComponent '背景色(実際に飾り付けを受けるコントロールの背景色) Public Property BackColor() As System.Drawing.Color Implements IComponent.BackColor Get Return Me.m_component.BackColor End Get Set(ByVal Value As System.Drawing.Color) Me.m_component.BackColor = Value End Set End Property 'フォント(実際に飾り付けを受けるコントロールのフォント) Public Property Font() As System.Drawing.Font Implements IComponent.Font Get Return Me.m_component.Font End Get Set(ByVal Value As System.Drawing.Font) Me.m_component.Font = Value End Set End Property '前面色(実際に飾り付けを受けるコントロールの前面色) Public Property ForeColor() As System.Drawing.Color Implements IComponent.ForeColor Get Return Me.m_component.ForeColor End Get Set(ByVal Value As System.Drawing.Color) Me.m_component.ForeColor = Value End Set End Property '装飾するメソッド定義(ここでは実装しない) Public MustOverride Sub Decolate() Implements IComponent.Decolate End Class
背景色の変更を担当するクラスです。
AbstractDecolator を継承しています。
'背景色の装飾 Public Class BackColorDecolator Inherits AbstractDecolator '背景色 Private m_backColor As Color 'コンストラクタ Public Sub New(ByVal component As IComponent, ByVal bkColor As Color) MyBase.m_component = component Me.m_backColor = bkColor End Sub '装飾を行うメソッド実装 Public Overrides Sub Decolate() If Not MyBase.m_component Is Nothing Then MyBase.m_component.Decolate() End If Me.m_component.BackColor = Me.m_backColor End Sub End Class
フォントの変更を担当するクラスです。
AbstractDecolator を継承しています。
'フォントの装飾 Public Class FontDecolator Inherits AbstractDecolator 'フォント Private m_font As Font 'コンストラクタ Public Sub New(ByVal component As IComponent, ByVal fnt As Font) MyBase.m_component = component Me.m_font = fnt End Sub '装飾を行うメソッド実装 Public Overrides Sub Decolate() If Not MyBase.m_component Is Nothing Then MyBase.m_component.Decolate() End If Me.m_component.Font = Me.m_font End Sub End Class
前面色の変更を担当するクラスです。
AbstractDecolator を継承しています。
'前面色の装飾 Public Class ForeColorDecolater Inherits AbstractDecolator '前面色 Private m_foreColor As Color 'コンストラクタ Public Sub New(ByVal component As IComponent, ByVal frColor As Color) MyBase.m_component = component Me.m_foreColor = frColor End Sub '装飾を行うメソッド実装 Public Overrides Sub Decolate() If Not MyBase.m_component Is Nothing Then MyBase.m_component.Decolate() End If Me.m_component.ForeColor = Me.m_foreColor End Sub End Class
■ひとこと
このデザインパターンのポイントは、StartUpForm の中で、変数 decolator を IComponent 型として宣言し、
インスタンス生成時に一工夫してある所です。
もし、背景色変更 の機能がいらなくなった場合は、
現在のインスタンス生成を行っている箇所の BackColorDecolator クラスのコンストラクタをはずします。
また新たに、装飾のクラスが加わった場合は、現在のインスタンス生成を行っている箇所に、更にそのクラスのコンストラクタを外側から
被せてやればよいのです。
もし、これを継承により実装していた場合、例えば、背景色 ← 前面色 ← フォント(親←子)という継承関係であった場合、
前面色の変更がいらなくなった場合、背景色のクラスとフォントのクラスを修正しなければなりません。
この様に、継承よりもより柔軟で、動的にも機能変更を可能とする方法を提供するのが Decolator パターンです。