Mediator (メディエータ) パターン
たった一人の相談役を設けて、色々なクラスからの相談を相談役が判定して、相談者に指示するデザインパターンです。
人前に出るのは苦手だけど、皆から慕われている上司が居たとしましょう。
Aさんは得意先のお客様から質問をうけましたが、どう答えたらよいのか解りません。
そこで、上司に相談します。上司の指示通りにお客様へ回答しました。
今度は、Bさんが飛び込みのお客様がやってきて、あれこれ聞かれています。
困ったBさんは、上司に相談します。上司は「Bは下がっててよいので、Cさんが対応しなさい」と命令します。
Aさん、Bさんは、頼りになる上司に判断を委ねています。
今回のサンプルは、再び Web アプリケーションです。
結城浩さんの「Java言語で学ぶデザインパターン入門」の中で、用いられている Mediator のサンプルを
ASP.NET に移植してみました。サーバーコントロールを用いて実装しています。
なお、Global.asax および Web.config ファイルはデフォルトのままなのでコードの掲載は省略しています。
■クラス図
ここで用いるサンプルのクラス図です。
準備中...
■サンプルの説明

ゲストの場合、ユーザー名とパスワードのテキストボックスは使用できなくなっています。

ゲストのラジオボタンがオンの状態で、OK ボタンを押下すると上記の画面へ遷移します。

登録ユーザーの場合、最初はパスワードのテキストボックスのみが使用できなくなっています。

登録ユーザーの場合、ユーザー名を入力してフォーカスを移動すると、パスワードのテキストボックスに入力する事ができます。

登録ユーザーのラジオボタンがオンの状態で、OK ボタンを押下すると上記の画面へ遷移します。
サンプルのプロジェクトダウンロード
■サーバーコントロールのコード
後に記述する、相談者から色々と相談を受ける役のインターフェースです。
'相談役 Public Interface IMediator '各メンバーを作成し、自分が相談役と言い渡すメソッド定義 Sub CreateColleagues() '各メンバーより相談された時のメソッド定義 Sub ColleagueChanged(ByVal colleague As MyMediatorControlLibrary.IColleague) End Interface
相談役に相談する相談者のインターフェースです。
'相談役に相談する人達 Public Interface IColleague '相談役を設定するメソッド定義 Sub SetMediator(ByVal mediator As IMediator) '相談役からの指示を受け取るメソッド定義 Sub SetColleagueEnabled(ByVal enabled As Boolean) End Interface
相談役に相談出来るようにした Button です。
IColleague を実装しています。
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 を実装しています。
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 を実装しています。
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 です。
<%@ 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 を実装しています。
'相談役 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 に格納されている値を表示します。
<%@ 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 のコードビハインドです。
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 の役目です。