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

ComboBox で監視する対象を選択します。
監視する対象に、キーボードで文字を入力すると、ComboBox の下に現在までの入力文字のバイト数と、
最後に押下されたキーの キーコードを表示します。
サンプルのプロジェクトダウンロード
■コード
アプリケーションのエントリポイント 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 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
監視される側のインターフェースです。
'監視されるモノ 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 を定義しています。
Public Interface IEditDataObserver '監視しているモノの状態を表示するメソッド定義 Sub Update(ByVal mySubject As IEditComponentSubject) '監視コントロールのサイズ Property Size() As Size '監視コントロールの位置 Property Location() As Point End Interface
監視されるモノの具象クラスの TextBox です。
IEditComponentSubject を実装しています。
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 を実装しています。
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 を実装しています。
'バイト数を監視する 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 を実装しています。
'キーコードを監視する 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 等のイベントハンドラのコード内で、一々全ての影響を与えたいコントロールに対して
操作を行うとしたらコーディングが大変なだけでなく、メンテナンス性も落ちてしまいます。
『監視されるモノ』と『監視するひと』のインターフェースを用意しておいてやる事によって、
『監視されるモノ』の状態の変更を、『監視するひと』が取得する事ができます。
自身の変更を通知する窓口を『監視されるモノ』が用意し、
それを受け取れる窓口を『監視するひと』が持っているところがミソです。