ソフトウェア設計

SwiftのMediatorパターン徹底解説(オブジェクト間通信を仲介者に任せる)

iOSアプリを少し成長させたことがある方なら、こんな場面に出会ったことがあるでしょう。

読了 5 分
SwiftのMediatorパターン徹底解説(オブジェクト間通信を仲介者に任せる)のカバー画像

iOSアプリを少し成長させたことがある方なら、こんな場面に出会ったことがあるでしょう。

1つの画面に5、6個のビューがあり、Aが変わるとBも変え、Bが変わるとCも直さなければならない状況です。

いつの間にかビュー同士が直接参照し合い、糸のように絡み合ってしまいます。

そんなときに整理してくれるのがMediatorパターンです。

SwiftのMediatorパターンは、オブジェクト同士を直接参照させず、Mediatorとのみ通信させることで結合度を下げる設計パターンです。

この記事では、Mediatorパターンとは何か、いつ使うべきか、Swiftでどう実装するかをサンプルコードとともに解説します。


Mediatorパターンとは?

一言で言うと、こうです。

通信が必要なオブジェクトの間に仲介者を置き、すべてのやり取りを仲介者経由にする方式です。

空港の管制塔にたとえられます。

飛行機同士が無線で「先に着陸して」「私が先に行く」と言い合えば、大事故になりますよね。

そこで、すべての飛行機は管制塔だけと話します。管制塔が順番を整理します。

ここで飛行機がコードのオブジェクト、管制塔がMediatorです。

Mediatorパターンが解決する核心は次のとおりです。

  • 直接参照の除去:オブジェクトAがB、C、Dを個別に知る必要がなくなる
  • 通信ロジックの一元化:複雑な相互作用のルールを1つのMediatorに集約する
  • 再利用性の向上:各オブジェクトが独立し、別の画面でも使いやすい

Mediatorパターンはいつ使うべき?

どんな状況にも適しているわけではありません。私は次のような兆候が見えたら導入を検討します。

オブジェクト同士が互いを知りすぎているとき、つまり1つのクラスを開くと他のオブジェクトへの参照が次々に出てくるときです。

もう1つは、1つのロジックを変えるために、関連する複数のオブジェクトを同時に修正しなければならないときです。

代表的な用途をまとめると次のとおりです。

状況 例
複雑な画面UI フォーム入力に応じて複数のボタン・ラベルが連鎖的に反応
コンポーネントの調整 チャットルーム参加者間のメッセージ中継
ワークフロー制御 決済の各段階におけるビュー状態の管理

逆に、オブジェクトが2、3個だけで関係も単純なら、無理に使わないほうがよいでしょう。

仲介者だけが肥大化し、「God Object」になりかねないからです。


SwiftのMediatorパターンはどう実装する?

チャットルームを例に見てみましょう。参加者同士が直接メッセージを送るのではなく、チャットルーム(Mediator)を介してやり取りする構造です。

まず、Mediatorが守るプロトコルと参加者を定義します。

// Mediatorが従うルール
protocol ChatMediator {
    func send(_ message: String, from user: User)
    func add(_ user: User)
}

参加者はMediatorだけを知り、他の参加者を直接参照しない点が重要です。

class User {
    let name: String
    weak var mediator: ChatMediator?  // 循環参照を防ぐ

    init(name: String) { self.name = name }

    func send(_ text: String) {
        mediator?.send(text, from: self)  // Mediatorに委譲
    }
    func receive(_ text: String) {
        print("\(name) 受信: \(text)")
    }
}

最後に、Mediatorが実際の配送ルールを担当します。送信者以外にだけメッセージを配信するロジックです。

class ChatRoom: ChatMediator {
    private var users: [User] = []
    func add(_ user: User) { users.append(user); user.mediator = self }
    func send(_ message: String, from user: User) {
        users.filter { $0 !== user }
             .forEach { $0.receive("[\(user.name)] \(message)") }
    }
}

ユーザーがどれだけ増えても、Userクラスを変更する必要はありません。配送ルールを知っているのはChatRoomだけだからです。

全員がChatRoomだけを知り、互いのことは知らない
全員がChatRoomだけを知り、互いのことは知らない
サンプルコードをそのまま動かして写経すると、感覚がつかめます
サンプルコードをそのまま動かして写経すると、感覚がつかめます

MediatorとObserverの違いは?

この2つを混同する方は本当に多いです。

違いを簡単にまとめると次のとおりです。

区分 Mediator Observer
方向性 多対多通信を中央集約 一対多通知(発行・購読)
関係 オブジェクトがMediatorを介して相互作用 観察対象が観察者に通知
焦点 複雑な相互「調整」 状態変化の「伝播」

簡単に言えば、Observerは「変わったよ!」と一方的に知らせる側です。

Mediatorは「この状況では誰が何をすべきか」を判断し、互いを調整する側です。

SwiftではCombineやNotificationCenterがObserver系に近く、Mediatorは通常自分で実装します。


よくある質問(Q&A)

Q. Mediatorが大きくなりすぎたら?

ロジックが集まるため、Mediatorは肥大化しやすいです。機能ごとに複数のMediatorへ分割するか、内部ロジックを別のヘルパーに分離しましょう。

Q. weakは必須ですか?

はい。参加者とMediatorが互いを強く参照すると、循環参照によってメモリーリークが発生します。例のように片方をweakにしてください。

Q. SwiftUIでも使えますか?

使えます。ViewModelがビュー間のMediatorの役割を実質的に担うことはよくあります。1つのViewModelで複数ビューの状態を調整する構造として自然に取り入れられます。

Mediatorを1つ描いて関係を整理したら、頭の中がすっきりしました
Mediatorを1つ描いて関係を整理したら、頭の中がすっきりしました

オブジェクトが絡み合い、コードに触るのが怖くなったら、Mediatorパターンを試してみてください。

Mediatorを1つ置くだけで各オブジェクトがずっと軽くなり、後から機能を追加するときも気が楽になります。

今日のサンプルコードをそのまま小さなプロジェクトに適用してみることをおすすめします。自分で書くと感覚がつかめます。😊

あわせて読みたい