【デザインパターン】Bridgeを学ぶ【C#】

C#

一言で言うと…

  • 「機能の抽象」と「実装の詳細」を分離し、それぞれを独立して拡張できる仕組み

概要

Bridge パターンは、GoF デザインパターンの一つで、構造に関するデザインパターンです。

クラスを「抽象(何をするか)」と「実装(どうやるか)」の2つの軸に分けてそれぞれを継承で拡張できるようにします。 2つの軸を橋(Bridge)でつないで連携させることで、両軸の組み合わせ爆発を防ぎます。

ゲーム開発では、「敵キャラの種類」と「移動AIアルゴリズム」のような、直交する2つの次元が出てきたときに有効です。

構成

classDiagram
    class Abstraction {
        # _impl : IImplementor
        + Operation()
    }
    class RefinedAbstraction {
        + Operation()
    }
    class IImplementor {
        << interface >>
        + OperationImpl()
    }
    class ConcreteImplementorA
    class ConcreteImplementorB

    RefinedAbstraction --|> Abstraction : extends
    Abstraction o-- IImplementor : bridge
    ConcreteImplementorA ..|> IImplementor : implements
    ConcreteImplementorB ..|> IImplementor : implements
    Client ..> Abstraction : << use >>
要素役割
Abstraction抽象側の基底クラス。IImplementor への参照を持つ
RefinedAbstraction抽象側の拡張クラス
IImplementor実装側のインターフェース
ConcreteImplementor実装側の具体クラス

実装例(C#)

クラス概要

早速実装例です。今回はC#でゲーム開発を行う場合を想定してみました。

利用ケースは、敵キャラの種類(ゴブリン・オーク)移動AIアルゴリズム(追跡・パトロール・ランダム) を独立して拡張できる機構です。

敵の種類が増えても、移動AIを変えずに済む。逆に移動AIを追加しても、敵クラスに手を加えなくてよいのがポイントです。

  • IMovementAI:移動アルゴリズムのインターフェース(IImplementor)
  • ChaseAIPatrolAIRandomAI:移動アルゴリズムの具象クラス(ConcreteImplementor)
  • Enemy:敵の抽象基底クラス(Abstraction)
  • GoblinOrc:敵の具体クラス(RefinedAbstraction)

ソースコード

1. 実装側インターフェース(IImplementor)

C#
/// <summary>
/// 移動AIインターフェース (IImplementor)
/// </summary>
public interface IMovementAI
{
    void Move(string enemyName, UnityEngine.Vector2 currentPos);
}

2. 具象実装(ConcreteImplementor)

C#
using System;
using UnityEngine;

/// <summary>プレイヤーを追いかける移動AI (Concrete Implementor A)</summary>
public class ChaseAI : IMovementAI
{
    private readonly Transform _playerTransform;

    public ChaseAI(Transform playerTransform)
    {
        _playerTransform = playerTransform;
    }

    public void Move(string enemyName, Vector2 currentPos)
    {
        var dir = ((Vector2)_playerTransform.position - currentPos).normalized;
        Console.WriteLine($"[追跡] {enemyName} がプレイヤー方向 {dir} へ移動");
    }
}

/// <summary>ルートを巡回するパトロールAI (Concrete Implementor B)</summary>
public class PatrolAI : IMovementAI
{
    private readonly Vector2[] _waypoints;
    private int _index;

    public PatrolAI(Vector2[] waypoints)
    {
        _waypoints = waypoints;
    }

    public void Move(string enemyName, Vector2 currentPos)
    {
        var target = _waypoints[_index % _waypoints.Length];
        _index++;
        Console.WriteLine($"[パトロール] {enemyName} がウェイポイント {target} へ移動");
    }
}

/// <summary>ランダムに動き回るAI (Concrete Implementor C)</summary>
public class RandomAI : IMovementAI
{
    private readonly System.Random _rng = new();

    public void Move(string enemyName, Vector2 currentPos)
    {
        var dir = new Vector2((float)(_rng.NextDouble() * 2 - 1),
                              (float)(_rng.NextDouble() * 2 - 1)).normalized;
        Console.WriteLine($"[ランダム] {enemyName} が方向 {dir} へ移動");
    }
}

3. 抽象基底クラス(Abstraction)

C#
/// <summary>
/// 敵の抽象クラス (Abstraction)
/// IMovementAI への参照を持ち、移動処理を委譲する
/// </summary>
public abstract class Enemy
{
    protected readonly IMovementAI _movementAI;
    protected readonly string Name;

    protected Enemy(string name, IMovementAI movementAI)
    {
        Name        = name;
        _movementAI = movementAI;
    }

    public abstract void Act(UnityEngine.Vector2 currentPos);
}

4. 拡張抽象クラス(RefinedAbstraction)

C#
using System;
using UnityEngine;

/// <summary>ゴブリン (Refined Abstraction)</summary>
public class Goblin : Enemy
{
    public Goblin(string name, IMovementAI movementAI) : base(name, movementAI) { }

    public override void Act(Vector2 currentPos)
    {
        Console.Write("[ゴブリン] ");
        _movementAI.Move(Name, currentPos);
    }
}

/// <summary>オーク (Refined Abstraction)</summary>
public class Orc : Enemy
{
    public Orc(string name, IMovementAI movementAI) : base(name, movementAI) { }

    public override void Act(Vector2 currentPos)
    {
        Console.Write("[オーク] ");
        _movementAI.Move(Name, currentPos);
    }
}

5. クライアントコード

C#
using UnityEngine;

class BridgeSample
{
    static void Main()
    {
        var playerTransform = new GameObject("Player").transform;
        var waypoints = new[] { new Vector2(0, 5), new Vector2(5, 5), new Vector2(5, 0) };

        // ゴブリンはプレイヤーを追いかける
        Enemy goblin = new Goblin("ゴブ太", new ChaseAI(playerTransform));
        goblin.Act(new Vector2(3, 3));
        // => [ゴブリン] [追跡] ゴブ太 がプレイヤー方向 (-0.7, 0.7) へ移動

        // オークはルートをパトロールする
        Enemy orc = new Orc("オークA", new PatrolAI(waypoints));
        orc.Act(new Vector2(0, 0));
        // => [オーク] [パトロール] オークA がウェイポイント (0, 5) へ移動

        // ゴブリンでもパトロールさせられる(組み合わせ自由)
        Enemy patrolGoblin = new Goblin("ゴブ次郎", new PatrolAI(waypoints));
        patrolGoblin.Act(new Vector2(1, 1));
        // => [ゴブリン] [パトロール] ゴブ次郎 がウェイポイント (0, 5) へ移動

        // 実行時にAIを差し替えることも可能(インターフェースへの参照を更新するなど)
    }
}

新しい敵クラスや移動AIを追加しても、もう一方の軸のクラスを変更する必要がありません。

使いどころ

  • 2つの独立した軸でクラスを拡張したい場合(例:敵キャラ種 × 移動AIアルゴリズム)
  • 継承による組み合わせ爆発を避けたいとき
  • 実装の詳細を実行時に切り替えたいとき

長所・短所

✅ 長所

  • 抽象と実装を独立して拡張できる(開放閉鎖の原則)
  • 継承の組み合わせ爆発を防げる
  • 実行時に実装を差し替えることができる

⚠️ 短所

  • クラスの分割数が増えるため、シンプルな設計では過剰になる
  • 設計の理解に慣れが必要で、初見では構造が複雑に見える

他GoFパターンとの比較・関係

Adapter との違い

Adapter は「すでに存在するクラスを別インターフェースに変換する」後付け対応です。

Bridge は「設計段階から抽象と実装を分ける」意図的な設計です。

Abstract Factory との関係

AbstractFactory を使って IImplementor の具体実装を生成することがあります。

Bridge の「実装側」をどのファクトリで生成するかを切り替えられます。

Strategy との違い

Strategy は「アルゴリズム(振る舞い)」を切り替えるパターンです。

Bridge は「実装の詳細」を抽象から分離するパターンです。

まとめ

  • Bridge パターンは、抽象と実装を独立した継承ツリーに分離する
  • 2つの軸の組み合わせ爆発を防ぎ、独立して拡張できる
  • ゲーム開発ではキャラクター種別と描画方式の分離など直交する軸に有効
  • Adapter は後付け変換、Bridge は設計段階からの分離という違いがある
  • Abstract Factory や Strategy と組み合わせて使われることも多い
タイトルとURLをコピーしました