Mediator (メディエータ) パターン

たった一人の相談役を設けて、色々なクラスからの相談を相談役が判定して、相談者に指示するデザインパターンです。
人前に出るのは苦手だけど、皆から慕われている上司が居たとしましょう。
Aさんは得意先のお客様から質問をうけましたが、どう答えたらよいのか解りません。
そこで、上司に相談します。上司の指示通りにお客様へ回答しました。
今度は、Bさんが飛び込みのお客様がやってきて、あれこれ聞かれています。
困ったBさんは、上司に相談します。上司は「Bは下がっててよいので、Cさんが対応しなさい」と命令します。
Aさん、Bさんは、頼りになる上司に判断を委ねています。


今回のサンプルは、再び Web アプリケーションです。
結城浩さんの「Java言語で学ぶデザインパターン入門」の中で、用いられている Mediator のサンプルを
ASP.NET に移植してみました。サーバーコントロールを用いて実装しています。
なお、Global.asax および Web.config ファイルはデフォルトのままなのでコードの掲載は省略しています。

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

■クラス図

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

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

■サンプルの説明

サンプルの見た目1
ゲストの場合、ユーザー名とパスワードのテキストボックスは使用できなくなっています。
サンプルの見た目2
ゲストのラジオボタンがオンの状態で、OK ボタンを押下すると上記の画面へ遷移します。
サンプルの見た目3
登録ユーザーの場合、最初はパスワードのテキストボックスのみが使用できなくなっています。
サンプルの見た目4
登録ユーザーの場合、ユーザー名を入力してフォーカスを移動すると、パスワードのテキストボックスに入力する事ができます。
サンプルの見た目5
登録ユーザーのラジオボタンがオンの状態で、OK ボタンを押下すると上記の画面へ遷移します。
サンプルのプロジェクトダウンロード

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

■サーバーコントロールのコード

後に記述する、相談者から色々と相談を受ける役のインターフェースです。

IMediator.vb
'相談役
Public Interface IMediator

  '各メンバーを作成し、自分が相談役と言い渡すメソッド定義
  Sub CreateColleagues()

  '各メンバーより相談された時のメソッド定義
  Sub ColleagueChanged(ByVal colleague As MyMediatorControlLibrary.IColleague)

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

相談役に相談する相談者のインターフェースです。

IColleague.vb
'相談役に相談する人達
Public Interface IColleague

  '相談役を設定するメソッド定義
  Sub SetMediator(ByVal mediator As IMediator)

  '相談役からの指示を受け取るメソッド定義
  Sub SetColleagueEnabled(ByVal enabled As Boolean)

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

相談役に相談出来るようにした Button です。
IColleague を実装しています。

ColleageButton.vb
Imports System.ComponentModel
Imports System.Web.UI

'Web カスタムコントロール ボタン
<DefaultProperty("Text"), ToolboxData("<{0}:ColleageButton runat=server></{0}:ColleageButton>")> _
Public Class ColleageButton
  Inherits System.Web.UI.WebControls.Button
  Implements IColleague

  '相談役
  Private m_mediator As IMediator

  '相談役を設定するメソッド実装
  Public Sub SetMediator(ByVal mediator As IMediator) Implements IColleague.SetMediator
    Me.m_mediator = mediator
  End Sub

  '相談役からの指示を受け取るメソッド実装
  Public Sub SetColleagueEnabled(ByVal enabled As Boolean) Implements IColleague.SetColleagueEnabled
    MyBase.Enabled = enabled
  End Sub

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

相談役に相談出来るようにした RadioButtonList です。
IColleague を実装しています。

ColleagueRadioButtonList.vb
Imports System.ComponentModel
Imports System.Web.UI

'Web カスタムコントロール ラジオボタンリスト
<ToolboxData("<{0}:ColleagueRadioButtonList runat=server></{0}:ColleagueRadioButtonList>")> _
Public Class ColleagueRadioButtonList
  Inherits System.Web.UI.WebControls.RadioButtonList
  Implements IColleague

  '相談役
  Private m_mediator As IMediator

  '相談役を設定するメソッド実装
  Public Sub SetMediator(ByVal mediator As IMediator) Implements IColleague.SetMediator
    Me.m_mediator = mediator
  End Sub

  '相談役からの指示を受け取るメソッド実装
  Public Sub SetColleagueEnabled(ByVal enabled As Boolean) Implements IColleague.SetColleagueEnabled
    MyBase.Enabled = enabled
  End Sub

  '選択したラジオボタンのインデックスが変更された時のイベント
  Protected Overrides Sub OnSelectedIndexChanged(ByVal e As System.EventArgs)
    MyBase.OnSelectedIndexChanged(e)

    If Not (Me.m_mediator Is Nothing) Then
      Me.m_mediator.ColleagueChanged(Me)
    End If
  End Sub
End Class
このカテゴリーの先頭へ このページの先頭へ

相談役に相談出来るようにした TextBox です。
IColleague を実装しています。

ColleagueTextBox.vb
Imports System.ComponentModel
Imports System.Web.UI

'Web カスタムコントロール テキストボックス
<ToolboxData("<{0}:ColleagueTextBox runat=server></{0}:ColleagueTextBox>")> _
Public Class ColleagueTextBox
  Inherits System.Web.UI.WebControls.TextBox
  Implements IColleague

  '相談役
  Private m_mediator As IMediator

  '相談役を設定するメソッド実装
  Public Sub SetMediator(ByVal mediator As IMediator) Implements IColleague.SetMediator
    Me.m_mediator = mediator
  End Sub

  '相談役からの指示を受け取るメソッド実装
  Public Sub SetColleagueEnabled(ByVal enabled As Boolean) Implements IColleague.SetColleagueEnabled
    MyBase.Enabled = enabled
  End Sub

  'テキストボックスに入力された値が変更し、フォーカス移動した時のイベント
  Protected Overrides Sub OnTextChanged(ByVal e As System.EventArgs)
    MyBase.OnTextChanged(e)
    If Not (Me.m_mediator Is Nothing) Then
      Me.m_mediator.ColleagueChanged(Me)
    End If
  End Sub
End Class
このカテゴリーの先頭へ このページの先頭へ

■コード

プログラムのスタートページで、相談を受ける役も務める MediatorForm です。

MediatorForm.aspx
<%@ Page Language="vb" AutoEventWireup="false" Codebehind="MediatorForm.aspx.vb" 
Inherits="MyMediator.MediatorForm"%>
<%@ Register TagPrefix="cc1" Namespace="MyMediatorControlLibrary" 
Assembly="MyMediatorControlLibrary" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
  <HEAD>
    <title>MediatorForm</title>
    <meta name="GENERATOR" content="Microsoft Visual Studio .NET 7.1">
    <meta name="CODE_LANGUAGE" content="Visual Basic .NET 7.1">
    <meta name="vs_defaultClientScript" content="JavaScript">
    <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">
  </HEAD>
  <body MS_POSITIONING="GridLayout">
    <form id="Form1" method="post" runat="server">
      <cc1:ColleagueRadioButtonList id="ColleagueRadioButtonList1" 
      style="Z-INDEX: 101; LEFT: 20px; POSITION: absolute; TOP: 19px" 
      runat="server"></cc1:ColleagueRadioButtonList>
      <asp:Label id="Label1" style="Z-INDEX: 102; LEFT: 17px; POSITION: absolute; TOP: 52px" runat="server">
      Label</asp:Label>
      <cc1:ColleagueTextBox id="ColleagueTextBox1" 
      style="Z-INDEX: 104; LEFT: 108px; POSITION: absolute; TOP: 46px" 
      runat="server"></cc1:ColleagueTextBox>
      <asp:Label id="Label2" style="Z-INDEX: 103; LEFT: 17px; POSITION: absolute; TOP: 80px" runat="server">
      Label</asp:Label>
      <cc1:ColleagueTextBox id="ColleagueTextBox2" 
      style="Z-INDEX: 105; LEFT: 107px; POSITION: absolute; TOP: 72px"
      runat="server"></cc1:ColleagueTextBox>
      <cc1:ColleageButton id="ColleageButton1" 
      style="Z-INDEX: 106; LEFT: 14px; POSITION: absolute; TOP: 112px"
      runat="server" Width="89px"></cc1:ColleageButton>
      <cc1:ColleageButton id="ColleageButton2" style="Z-INDEX: 107; LEFT: 112px; POSITION: absolute; TOP: 112px"
      runat="server" Width="89px"></cc1:ColleageButton>
    </form>
  </body>
</HTML>
このカテゴリーの先頭へ このページの先頭へ

MediatorForm のコードビハインドです。
IMediator を実装しています。

MediatorForm.aspx.vb
'相談役
Public Class MediatorForm
  Inherits System.Web.UI.Page
  Implements MyMediatorControlLibrary.IMediator

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

  'この呼び出しは Web フォーム デザイナで必要です。
  <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()

  End Sub
  Protected WithEvents ColleagueRadioButtonList1 As MyMediatorControlLibrary.ColleagueRadioButtonList
  Protected WithEvents Label1 As System.Web.UI.WebControls.Label
  Protected WithEvents Label2 As System.Web.UI.WebControls.Label
  Protected WithEvents ColleagueTextBox1 As MyMediatorControlLibrary.ColleagueTextBox
  Protected WithEvents ColleagueTextBox2 As MyMediatorControlLibrary.ColleagueTextBox
  Protected WithEvents ColleageButton1 As MyMediatorControlLibrary.ColleageButton
  Protected WithEvents ColleageButton2 As MyMediatorControlLibrary.ColleageButton

  'メモ : 次のプレースホルダ宣言は Web フォーム デザイナで必要です。
  '削除および移動しないでください。
  Private designerPlaceholderDeclaration As System.Object

  Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init
    ' CODEGEN: このメソッド呼び出しは Web フォーム デザイナで必要です。
    ' コード エディタを使って変更しないでください。
    InitializeComponent()

    CreateColleagues()
    ColleagueChanged(Me.ColleagueRadioButtonList1)
  End Sub

#End Region

  'ゲスト
  Private Const GEST As Integer = 0
  '登録ユーザー
  Private Const LOGIN_USER As Integer = 1

  'ページがロードされた時のイベント
  Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    '初回ロード時にCreateColleaguesを呼び出す
    If Not IsPostBack Then
      Me.CreateColleagues()
    End If
  End Sub

  '各メンバーより相談された時のメソッド実装
  Public Sub ColleagueChanged(ByVal colleague As MyMediatorControlLibrary.IColleague) _
                     Implements MyMediatorControlLibrary.IMediator.ColleagueChanged
    If colleague Is Me.ColleagueRadioButtonList1 Then
      If (Me.ColleagueRadioButtonList1.SelectedIndex = 0) Then
        Me.ColleagueTextBox1.SetColleagueEnabled(False)
        Me.ColleagueTextBox2.SetColleagueEnabled(False)
        Me.ColleageButton1.SetColleagueEnabled(False)
      Else
        Me.ColleagueTextBox1.SetColleagueEnabled(True)
        Me.UserPassChanged()
      End If
    ElseIf (colleague Is Me.ColleagueTextBox1) OrElse (colleague Is Me.ColleagueTextBox2) Then
      Me.UserPassChanged()
    End If
  End Sub

  'ユーザー・パスワードが変更された時にTextBoxの状態の切り替え
  Private Sub UserPassChanged()
    If (Me.ColleagueTextBox1.Text.Length > 0) Then
      Me.ColleagueTextBox2.SetColleagueEnabled(True)
      Me.ColleageButton1.SetColleagueEnabled(Me.ColleagueTextBox2.Text.Length > 0)
    Else
      Me.ColleagueTextBox2.SetColleagueEnabled(False)
      Me.ColleageButton1.SetColleagueEnabled(False)
    End If
  End Sub

  '各メンバーを作成し、自分が相談役と言い渡すメソッド実装
  Public Sub CreateColleagues() Implements MyMediatorControlLibrary.IMediator.CreateColleagues

    Me.ColleagueRadioButtonList1.Items.Clear()
    Me.ColleagueRadioButtonList1.Items.Add(New ListItem("ゲスト"))
    Me.ColleagueRadioButtonList1.Items.Add(New ListItem("登録ユーザー"))
    Me.ColleagueRadioButtonList1.RepeatDirection = RepeatDirection.Horizontal
    Me.ColleagueRadioButtonList1.SelectedIndex = Me.GEST
    Me.ColleagueRadioButtonList1.AutoPostBack = True

    Me.Label1.Text = "ユーザー名"
    Me.Label2.Text = "パスワード"
    Me.ColleagueTextBox1.Text = ""
    Me.ColleagueTextBox1.AutoPostBack = True
    Me.ColleagueTextBox2.Text = ""
    Me.ColleagueTextBox2.AutoPostBack = True
    Me.ColleageButton1.Text = "OK"
    Me.ColleageButton2.Text = "キャンセル"

    Me.ColleagueRadioButtonList1.SetMediator(Me)
    Me.ColleagueTextBox1.SetMediator(Me)
    Me.ColleagueTextBox2.SetMediator(Me)
    Me.ColleageButton1.SetMediator(Me)
    Me.ColleageButton2.SetMediator(Me)
  End Sub

  'OKボタン・キャンセルボタン押下時のイベント
  Private Sub Button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
                  Handles ColleageButton1.Click, ColleageButton2.Click
    If sender Is ColleageButton1 Then
      If Me.GEST = Me.ColleagueRadioButtonList1.SelectedIndex Then
        Session("userName") = Nothing
      Else
        Session("userName") = Me.ColleagueTextBox1.Text
      End If
      '次画面へ遷移
      Response.Redirect("WebForm2.aspx")
    Else
      Me.ColleagueRadioButtonList1.SelectedIndex = Me.GEST
      Me.ColleagueTextBox1.Text = ""
      Me.ColleagueTextBox2.Text = ""
      Me.ColleagueChanged(Me.ColleagueRadioButtonList1)
    End If
  End Sub
End Class
このカテゴリーの先頭へ このページの先頭へ

おまけの WebForm2 です。
MediatorForm から画面遷移し、Session に格納されている値を表示します。

WebForm2.aspx
<%@ Page Language="vb" AutoEventWireup="false" Codebehind="WebForm2.aspx.vb" Inherits="MyMediator.WebForm2"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
  <HEAD>
    <title>WebForm2</title>
    <meta name="GENERATOR" content="Microsoft Visual Studio .NET 7.1">
    <meta name="CODE_LANGUAGE" content="Visual Basic .NET 7.1">
    <meta name="vs_defaultClientScript" content="JavaScript">
    <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">
  </HEAD>
  <body MS_POSITIONING="GridLayout">
    <form id="Form1" method="post" runat="server">
      <FONT face="MS UI Gothic">
        <asp:Label id="Label1" style="Z-INDEX: 101; LEFT: 29px; POSITION: absolute; TOP: 37px" runat="server"
          Width="556px">Label</asp:Label></FONT>
    </form>
  </body>
</HTML>
このカテゴリーの先頭へ このページの先頭へ

おまけの WebForm2 のコードビハインドです。

WebForm2.aspx.vb
Public Class WebForm2
  Inherits System.Web.UI.Page

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

  'この呼び出しは Web フォーム デザイナで必要です。
  <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()

  End Sub
  Protected WithEvents Label1 As System.Web.UI.WebControls.Label

  'メモ : 次のプレースホルダ宣言は Web フォーム デザイナで必要です。
  '削除および移動しないでください。
  Private designerPlaceholderDeclaration As System.Object

  Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init
    ' CODEGEN: このメソッド呼び出しは Web フォーム デザイナで必要です。
    ' コード エディタを使って変更しないでください。
    InitializeComponent()
  End Sub

#End Region

  'ページがロードされた時のイベント
  Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    '初回のページロードの時にセッションからユーザー名を取得して表示する
    If Not IsPostBack Then
      Dim userName As String
      If Not Session("userName") Is Nothing Then
        userName = DirectCast(Session("userName"), String)
      Else
        userName = "ゲスト"
      End If
      Me.Label1.Text = "ようこそ" + userName + "さん!"
    End If
  End Sub

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

■ひとこと

やりながら気づいたのですが、結構大変な割には
このサンプルを ASP.NET でやるのは、レスポンスタイムを考えると、非実用的かもしれません。orz


しかし、しかーし。このサンプルを、Mediator を用意せず、相談者同士で他の相談者の状態を見て
実装するとしたら、スパゲッティになるのが容易に想像できますね。現実世界でもそうですよね。
相談者同士の結合を疎にし、相談者自身が自身の状態の変更によって周りにどの様な影響を与えなければならないか...
それを一挙に引き受けて、他の相談者に隠蔽するのが Mediator の役目です。

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