💎
設計課題から理解するデザインパターンの本質 #11 Mediator
🎯設計課題
多対多のオブジェクト間依存を排除し、やり取りを仲介者に集約することで、オブジェクト同士を疎結合に保ちつつ処理の見通しをよくしたい。
例えば認証ダイアログでは、チェックボックス・ログインフォーム・登録フォームが互いに連動する。これを各オブジェクトが直接参照し合うかたちで実装すると、依存が絡み合って変更が困難になる。
🧩登場する要素と役割
- Mediator(AuthDialog): Colleague からのイベントを受け取り、他の Colleague を制御する
- Colleague(Colleague): Mediator への参照を持つ基底クラス
- ConcreteColleague(LoginCheckbox, LoginForm, RegisterForm): 自身の操作後に Mediator へイベントを通知する
-
NullComponent: 初期化時のプレースホルダー。
enable/disableを no-op にする Null Object
🪶サンプルコード
テスト
test/mediator/auth_dialog_test.rb
require_relative "../test_helper"
require_relative "../../lib/mediator/auth_dialog"
class AuthDialogTest < Minitest::Test
def test_check_checkbox
auth_dialog = AuthDialog.new
refute auth_dialog.login_checkbox.checked?
refute auth_dialog.login_form.enabled?
assert auth_dialog.register_form.enabled?
auth_dialog.login_checkbox.check
assert auth_dialog.login_checkbox.checked?
assert auth_dialog.login_form.enabled?
refute auth_dialog.register_form.enabled?
end
def test_uncheck_checkbox
auth_dialog = AuthDialog.new
auth_dialog.login_checkbox.check
assert auth_dialog.login_checkbox.checked?
assert auth_dialog.login_form.enabled?
refute auth_dialog.register_form.enabled?
auth_dialog.login_checkbox.uncheck
refute auth_dialog.login_checkbox.checked?
refute auth_dialog.login_form.enabled?
assert auth_dialog.register_form.enabled?
end
def test_submit_register_form
auth_dialog = AuthDialog.new
refute auth_dialog.login_checkbox.checked?
refute auth_dialog.login_form.enabled?
assert auth_dialog.register_form.enabled?
auth_dialog.register_form.submit
assert auth_dialog.login_checkbox.checked?
assert auth_dialog.login_form.enabled?
refute auth_dialog.register_form.enabled?
end
end
アプリケーション
lib/mediator/component.rb
module Component
def enable = @enabled = true
def disable = @enabled = false
def enabled? = @enabled
end
lib/mediator/null_component.rb
require_relative "component"
class NullComponent
include Component
def enable; end
def disable; end
def enabled? = false
end
lib/mediator/colleague.rb
class Colleague
def initialize(mediator:)
@mediator = mediator
end
end
lib/mediator/login_checkbox.rb
require_relative "colleague"
class LoginCheckbox < Colleague
def check
@checked = true
@mediator.event_from(self, type: :check)
self
end
def uncheck
@checked = false
@mediator.event_from(self, type: :uncheck)
self
end
def checked? = @checked
end
lib/mediator/login_form.rb
require_relative "colleague"
require_relative "component"
class LoginForm < Colleague
include Component
end
lib/mediator/register_form.rb
require_relative "colleague"
require_relative "component"
class RegisterForm < Colleague
include Component
def submit
@mediator.event_from(self, type: :submit)
end
end
lib/mediator/auth_dialog.rb
require_relative "null_component"
require_relative "login_checkbox"
require_relative "login_form"
require_relative "register_form"
class AuthDialog
attr_reader :login_checkbox, :login_form, :register_form
def initialize
@login_checkbox = NullComponent.new
@login_form = NullComponent.new
@register_form = NullComponent.new
@login_checkbox = LoginCheckbox.new(mediator: self).uncheck
@login_form = LoginForm.new(mediator: self).tap(&:disable)
@register_form = RegisterForm.new(mediator: self).tap(&:enable)
end
def event_from(object, type:)
case [object.class, type]
in [LoginCheckbox, :check]
@login_form.enable
@register_form.disable
in [LoginCheckbox, :uncheck]
@login_form.disable
@register_form.enable
in [RegisterForm, :submit]
@login_checkbox.check
@login_form.enable
end
end
end
💎パターンの本質
LoginCheckbox は LoginForm を知らない。RegisterForm も LoginCheckbox を知らない。各 Colleague は「自分に何かが起きたら Mediator に通知する」だけで、その後の連動ロジックはすべて AuthDialog#event_from に集まっている。Colleague 同士を直接依存させず、やり取りをすべて Mediator に集約することが Mediator パターンの本質。
Discussion