Decorator (デコレータ) パターン

Decorator → Decoration と聞くと思い浮かぶのは...?きれいに飾り付けられたケーキですね。
飾り付けられるもの(スポンジ)と、追加してつけるもの(生クリームや苺などの装飾)を同じように扱う事によって、
機能の追加を柔軟に行うデザインパターンです。


サンプルでは、Button や TextBox や Label を、上記例でいうとスポンジに見立て、
ユーザーが選んだ装飾を動的に施しています。
サンプルの中で使用している、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 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
このカテゴリーの先頭へ このページの先頭へ

飾られるモノと飾るモノを同一視するためのインターフェースです。

AbstractComponent.vb
'飾られるモノと飾るモノを同一視する為のインターフェース
Public Interface IComponent
  '背景色
  Property BackColor() As Color
  'フォント
  Property Font() As Font
  '前面色
  Property ForeColor() As Color
  '装飾を行うメソッド定義
  Sub Decolate()
End Interface
このカテゴリーの先頭へ このページの先頭へ

実際に飾られるモノを保持するクラスです。
IComponent を実装しています。

PureControl.vb
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 メソッド以外を実装しています。

AbstractDecolator.vb
'飾り付けを行うクラスの抽象 基底クラス
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 を継承しています。

BackColorDecolator.vb
'背景色の装飾
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 を継承しています。

FontDecolator.vb
'フォントの装飾
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 を継承しています。

ForeColorDecolator.vb
'前面色の装飾
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 パターンです。

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