Observer (オブザーバ) パターン

あるオブジェクトの状態の変化を監視し、それと依存関係にあるオブジェクトに対して自動的に知らされるしくみのデザインパターンです。
監視というよりも、正確には『通知』と『通知の受け取り』です。
ユーザが、マウスやキーボードで操作して、状態が変更されるようなオブジェクトの状態を
リアルタイムで取得して、他のオブジェクトに反映をしたい場合に用いるデザインパターンです。


サンプルでは、TextBox や RichTextBox にユーザーがキー入力した状態をリアルタイムで取得します。

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

■クラス図

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

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

■サンプルの説明

サンプルの見た目1
ComboBox で監視する対象を選択します。
監視する対象に、キーボードで文字を入力すると、ComboBox の下に現在までの入力文字のバイト数と、
最後に押下されたキーの キーコードを表示します。
サンプルのプロジェクトダウンロード

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

■コード

アプリケーションのエントリポイント 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 ComboBox1 As System.Windows.Forms.ComboBox
  Friend WithEvents Panel1 As System.Windows.Forms.Panel
  Friend WithEvents Label3 As System.Windows.Forms.Label
  Friend WithEvents Label2 As System.Windows.Forms.Label
  Friend WithEvents RichTextBox1 As System.Windows.Forms.RichTextBox
  <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
    Me.Label1 = New System.Windows.Forms.Label
    Me.ComboBox1 = New System.Windows.Forms.ComboBox
    Me.Panel1 = New System.Windows.Forms.Panel
    Me.Label3 = New System.Windows.Forms.Label
    Me.Label2 = New System.Windows.Forms.Label
    Me.RichTextBox1 = New System.Windows.Forms.RichTextBox
    Me.Panel1.SuspendLayout()
    Me.SuspendLayout()
    '
    'Label1
    '
    Me.Label1.Location = New System.Drawing.Point(8, 8)
    Me.Label1.Name = "Label1"
    Me.Label1.TabIndex = 0
    Me.Label1.Text = "Label1"
    '
    'ComboBox1
    '
    Me.ComboBox1.Location = New System.Drawing.Point(112, 8)
    Me.ComboBox1.Name = "ComboBox1"
    Me.ComboBox1.Size = New System.Drawing.Size(121, 20)
    Me.ComboBox1.TabIndex = 1
    Me.ComboBox1.Text = "ComboBox1"
    '
    'Panel1
    '
    Me.Panel1.Controls.Add(Me.RichTextBox1)
    Me.Panel1.Controls.Add(Me.Label3)
    Me.Panel1.Controls.Add(Me.Label2)
    Me.Panel1.Location = New System.Drawing.Point(8, 40)
    Me.Panel1.Name = "Panel1"
    Me.Panel1.Size = New System.Drawing.Size(272, 216)
    Me.Panel1.TabIndex = 2
    '
    'Label3
    '
    Me.Label3.Location = New System.Drawing.Point(10, 40)
    Me.Label3.Name = "Label3"
    Me.Label3.Size = New System.Drawing.Size(240, 20)
    Me.Label3.TabIndex = 1
    Me.Label3.Text = "Label3"
    '
    'Label2
    '
    Me.Label2.Location = New System.Drawing.Point(10, 10)
    Me.Label2.Name = "Label2"
    Me.Label2.Size = New System.Drawing.Size(240, 20)
    Me.Label2.TabIndex = 0
    Me.Label2.Text = "Label2"
    '
    'RichTextBox1
    '
    Me.RichTextBox1.Location = New System.Drawing.Point(10, 70)
    Me.RichTextBox1.Name = "RichTextBox1"
    Me.RichTextBox1.Size = New System.Drawing.Size(240, 130)
    Me.RichTextBox1.TabIndex = 2
    Me.RichTextBox1.Text = "RichTextBox1"
    '
    'StartUpForm
    '
    Me.AutoScaleBaseSize = New System.Drawing.Size(5, 12)
    Me.ClientSize = New System.Drawing.Size(292, 266)
    Me.Controls.Add(Me.Panel1)
    Me.Controls.Add(Me.ComboBox1)
    Me.Controls.Add(Me.Label1)
    Me.Name = "StartUpForm"
    Me.Text = "Form1"
    Me.Panel1.ResumeLayout(False)
    Me.ResumeLayout(False)

  End Sub

#End Region

  'ドロップダウンリストの値
  Private Const KIND_TEXT As String = "TextBox"
  Private Const KIND_RICHTEXT As String = "RichTextBox"

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

    Me.Label1.Text = "監視対象"

    Me.ComboBox1.Items.Clear()
    Me.ComboBox1.DropDownStyle = ComboBoxStyle.DropDownList
    Me.ComboBox1.Items.Add(Me.KIND_TEXT)
    Me.ComboBox1.Items.Add(Me.KIND_RICHTEXT)
    Me.ComboBox1.SelectedIndex = 0
  End Sub

  '監視対象ComboBoxの選択値変更時のイベント
  Private Sub ComboBox1_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) _
                                Handles ComboBox1.SelectedIndexChanged
    Me.Panel1.Controls.Clear()
    '監視されるモノ
    Dim mySubject As IEditComponentSubject
    If Me.ComboBox1.Text = Me.KIND_TEXT Then
      mySubject = New TextBoxSubject
      mySubject.Location = New Point(10, 70)
      mySubject.Size = New Size(240, 20)
    Else
      mySubject = New RichTextBoxBoxSubject
      mySubject.Location = New Point(10, 70)
      mySubject.Size = New Size(240, 130)
    End If

    '監視者達
    Dim myByteObserver As IEditDataObserver = New ByteCountObserver
    myByteObserver.Location = New Point(10, 10)
    myByteObserver.Size = New Size(240, 20)

    Dim myCharCodeObserver As IEditDataObserver = New KeyCodeObserver
    myCharCodeObserver.Location = New Point(10, 40)
    myCharCodeObserver.Size = New Size(240, 20)

    '監視者を追加
    mySubject.AddObserver(myByteObserver)
    mySubject.AddObserver(myCharCodeObserver)

    'Panelコントロールに追加する
    Me.Panel1.Controls.Clear()
    Me.Panel1.Controls.Add(DirectCast(myByteObserver, Control))
    Me.Panel1.Controls.Add(DirectCast(myCharCodeObserver, Control))
    Me.Panel1.Controls.Add(DirectCast(mySubject, Control))
  End Sub
End Class
このカテゴリーの先頭へ このページの先頭へ

監視される側のインターフェースです。

IEditComponentSubject.vb
'監視されるモノ
Public Interface IEditComponentSubject

  '監視者を追加するメソッド定義
  Sub AddObserver(ByVal myObserver As IEditDataObserver)
  '監視者達に通知するメソッド定義
  Sub NotifyObservers()
  '現在の全文字を返却するメソッド定義
  Function CurrentString() As String
  '現在の文字を返却するメソッド定義
  Function CurrentKey() As Keys
  '自身の状態が変化した時に監視者に通知するメソッド定義
  Sub Change(ByVal e As System.Windows.Forms.KeyEventArgs)
  'コントロールのサイズ
  Property Size() As Size
  'コントロールの位置
  Property Location() As Point

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

監視する側のインターフェースです。
監視者は具体的にはこのサンプルではコントロールとした為、Size 及び Location のような
Property を定義しています。

IEditDataObserver.vb
Public Interface IEditDataObserver

  '監視しているモノの状態を表示するメソッド定義
  Sub Update(ByVal mySubject As IEditComponentSubject)
  '監視コントロールのサイズ
  Property Size() As Size
  '監視コントロールの位置
  Property Location() As Point

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

監視されるモノの具象クラスの TextBox です。
IEditComponentSubject を実装しています。

TextBoxSubject.vb
Public Class TextBoxSubject
  Inherits TextBox
  Implements IEditComponentSubject

  '最後に入力されたキー
  Private m_currentKey As Keys
  '自分を監視する人たち(自身の状態が変わったら通知する人たち)
  Private m_observers As ArrayList

  'コンストラクタ
  Public Sub New()
    MyBase.New()
    Me.m_observers = New ArrayList
  End Sub

  '現在の全文字を返却するメソッド実装
  Public Function CurrentString() As String Implements IEditComponentSubject.CurrentString
    Return MyBase.Text
  End Function

  '現在のキーを返却するメソッド実装
  Public Function CurrentKey() As Keys Implements IEditComponentSubject.CurrentKey
    Return Me.m_currentKey
  End Function

  '監視する人を追加する
  Public Sub AddObserver(ByVal myObserver As IEditDataObserver) Implements IEditComponentSubject.AddObserver
    Me.m_observers.Add(myObserver)
  End Sub

  '監視者を追加するメソッド実装
  Public Sub NotifyObservers() Implements IEditComponentSubject.NotifyObservers
    For i As Integer = 0 To Me.m_observers.Count - 1
      Dim currentObserver As IEditDataObserver = DirectCast(Me.m_observers(i), IEditDataObserver)
      currentObserver.Update(Me)
    Next
  End Sub

  '監視者達に通知するメソッド実装
  Protected Overrides Sub OnKeyUp(ByVal e As System.Windows.Forms.KeyEventArgs) _
                     Implements IEditComponentSubject.Change
    Me.m_currentKey = e.KeyCode
    Me.NotifyObservers()
  End Sub

  'コントロールの位置
  Public Shadows Property Location() As System.Drawing.Point Implements IEditComponentSubject.Location
    Get
      Return MyBase.Location
    End Get
    Set(ByVal Value As System.Drawing.Point)
      MyBase.Location = Value
    End Set
  End Property

  'コントロールのサイズ
  Public Shadows Property Size() As System.Drawing.Size Implements IEditComponentSubject.Size
    Get
      Return MyBase.Size
    End Get
    Set(ByVal Value As System.Drawing.Size)
      MyBase.Size = Value
    End Set
  End Property
End Class
このカテゴリーの先頭へ このページの先頭へ

監視されるモノの具象クラスの RichTextBox です。
IEditComponentSubject を実装しています。

RichTextBoxBoxSubject.vb
Public Class RichTextBoxBoxSubject
  Inherits RichTextBox
  Implements IEditComponentSubject

  '最後に入力されたキー
  Private m_currentKey As Keys
  '自分を監視する人たち(自身の状態が変わったら通知する人たち)
  Private m_observers As ArrayList

  'コンストラクタ
  Public Sub New()
    MyBase.New()
    Me.m_observers = New ArrayList
  End Sub

  '現在の全文字を返却するメソッド実装
  Public Function CurrentString() As String Implements IEditComponentSubject.CurrentString
    Return MyBase.Text
  End Function

  '現在のキーを返却するメソッド実装
  Public Function CurrentKey() As Keys Implements IEditComponentSubject.CurrentKey
    Return Me.m_currentKey
  End Function

  '監視する人を追加する
  Public Sub AddObserver(ByVal myObserver As IEditDataObserver) Implements IEditComponentSubject.AddObserver
    Me.m_observers.Add(myObserver)
  End Sub

  '監視者を追加するメソッド実装
  Public Sub NotifyObservers() Implements IEditComponentSubject.NotifyObservers
    For i As Integer = 0 To Me.m_observers.Count - 1
      Dim currentObserver As IEditDataObserver = DirectCast(Me.m_observers(i), IEditDataObserver)
      currentObserver.Update(Me)
    Next
  End Sub

  '監視者達に通知するメソッド実装
  Protected Overrides Sub OnKeyUp(ByVal e As System.Windows.Forms.KeyEventArgs) _
                     Implements IEditComponentSubject.Change
    Me.m_currentKey = e.KeyCode
    Me.NotifyObservers()
  End Sub

  'コントロールの位置
  Public Shadows Property Location() As System.Drawing.Point Implements IEditComponentSubject.Location
    Get
      Return MyBase.Location
    End Get
    Set(ByVal Value As System.Drawing.Point)
      MyBase.Location = Value
    End Set
  End Property

  'コントロールのサイズ
  Public Shadows Property Size() As System.Drawing.Size Implements IEditComponentSubject.Size
    Get
      Return MyBase.Size
    End Get
    Set(ByVal Value As System.Drawing.Size)
      MyBase.Size = Value
    End Set
  End Property
End Class
このカテゴリーの先頭へ このページの先頭へ

監視するモノの具象クラスの ByteCountObserver です。
IEditDataObserver を実装しています。

ByteCountObserver.vb
'バイト数を監視する
Public Class ByteCountObserver
  Inherits Label
  Implements IEditDataObserver

  '監視しているモノの状態を表示するメソッド実装
  Public Shadows Sub Update(ByVal mySubject As IEditComponentSubject) Implements IEditDataObserver.Update
    Dim byteCount As Integer = System.Text.Encoding.Default.GetByteCount(mySubject.CurrentString)
    '現在のバイト数を表示
    MyBase.Text = "現在のバイト数は " + byteCount.ToString() + " です"
    MyBase.Update()
  End Sub

  '監視コントロールの位置
  Public Shadows Property Location() As System.Drawing.Point Implements IEditComponentSubject.Location
    Get
      Return MyBase.Location
    End Get
    Set(ByVal Value As System.Drawing.Point)
      MyBase.Location = Value
    End Set
  End Property

  '監視コントロールのサイズ
  Public Shadows Property Size() As System.Drawing.Size Implements IEditComponentSubject.Size
    Get
      Return MyBase.Size
    End Get
    Set(ByVal Value As System.Drawing.Size)
      MyBase.Size = Value
    End Set
  End Property
End Class
このカテゴリーの先頭へ このページの先頭へ

監視するモノの具象クラスの KeyCodeObserver です。
IEditDataObserver を実装しています。

KeyCodeObserver.vb
'キーコードを監視する
Public Class KeyCodeObserver
  Inherits Label
  Implements IEditDataObserver

  '監視しているモノの状態を表示するメソッド実装
  Public Shadows Sub Update(ByVal mySubject As IEditComponentSubject) Implements IEditDataObserver.Update

    '入力文字のキーコードを表示
    Dim byteValue As Byte() = System.Text.Encoding.Default.GetBytes(mySubject.CurrentKey.ToString())
    If byteValue.Length > 0 Then
      MyBase.Text = "入力文字のキーコードは " + byteValue(0).ToString() + " です"
    Else
      MyBase.Text = ""
    End If
    MyBase.Update()
  End Sub

  '監視コントロールの位置
  Public Shadows Property Location() As System.Drawing.Point Implements IEditComponentSubject.Location
    Get
      Return MyBase.Location
    End Get
    Set(ByVal Value As System.Drawing.Point)
      MyBase.Location = Value
    End Set
  End Property

  '監視コントロールのサイズ
  Public Shadows Property Size() As System.Drawing.Size Implements IEditComponentSubject.Size
    Get
      Return MyBase.Size
    End Get
    Set(ByVal Value As System.Drawing.Size)
      MyBase.Size = Value
    End Set
  End Property
End Class
このカテゴリーの先頭へ このページの先頭へ

■ひとこと

まぁ、そんな面倒な事はする人は居ないと思いますが、
TextBox 等のイベントハンドラのコード内で、一々全ての影響を与えたいコントロールに対して
操作を行うとしたらコーディングが大変なだけでなく、メンテナンス性も落ちてしまいます。


『監視されるモノ』と『監視するひと』のインターフェースを用意しておいてやる事によって、
『監視されるモノ』の状態の変更を、『監視するひと』が取得する事ができます。
自身の変更を通知する窓口を『監視されるモノ』が用意し、 それを受け取れる窓口を『監視するひと』が持っているところがミソです。

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