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

C#

一言で言うと…

  • 既存クラスのインターフェースを、クライアントが期待する別のインターフェースに変換する仕組み

概要

Adapter パターンは、GoF デザインパターンの一つで、構造に関するデザインパターンです。 別名 Wrapper とも呼ばれます。

互いに互換性のないインターフェースを持つクラス同士を、中間の「アダプター」を挟むことで連携させます。
既存クラスを改変せずに、新しいインターフェースへ対応させられるのが最大のメリットです。

ゲーム開発では、旧バージョンの武器システムを新しい戦闘システムに組み込みたい場面などで役立ちます。

構成

classDiagram
    class ITarget {
        << interface >>
        + Request()
    }
    class Adapter {
        - _adaptee : Adaptee
        + Request()
    }
    class Adaptee {
        + SpecificRequest()
    }
    Adapter ..|> ITarget : implements
    Adapter o-- Adaptee : wraps
    Client ..> ITarget : << use >>
要素役割
ITargetクライアントが期待するインターフェース
Adaptee既存の(互換性のない)クラス
AdapterAdaptee を ITarget に変換する中間クラス
ClientITarget 経由で利用する側

実装例(C#)

クラス概要

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

利用ケースは以下のように、旧システムの武器クラスを新戦闘システムのインターフェースに適合させる機構です。

  • IWeapon:新システムが期待するインターフェース(ITarget)
  • LegacySword:旧システムの剣クラス(Adaptee)
  • LegacySwordAdapter:旧剣を新インターフェースに変換するアダプター(Adapter)

ソースコード

1. 新インターフェース(ITarget)

C#
/// <summary>
/// 新戦闘システムが期待する武器インターフェース (ITarget)
/// </summary>
public interface IWeapon
{
    string Name { get; }

    /// <summary>攻撃を実行する</summary>
    void Attack(string targetName);
}

2. 旧システムのクラス(Adaptee)

C#
using System;

/// <summary>
/// 旧システムの剣クラス (Adaptee)
/// 新インターフェース IWeapon とは互換性がない
/// </summary>
public class LegacySword
{
    public string SwordName { get; }
    public int Damage { get; }

    public LegacySword(string name, int damage)
    {
        SwordName = name;
        Damage    = damage;
    }

    /// <summary>旧システムの攻撃メソッド(引数や戻り値が新システムと異なる)</summary>
    public void Slash(string enemy, int power)
    {
        Console.WriteLine($"[旧システム] {SwordName} で {enemy} に {power} ダメージ与えた!");
    }
}

3. アダプター(Adapter)

C#
/// <summary>
/// 旧システムの剣を新インターフェースに適合させるアダプター (Adapter)
/// </summary>
public class LegacySwordAdapter : IWeapon
{
    private readonly LegacySword _legacySword;

    public LegacySwordAdapter(LegacySword legacySword)
    {
        _legacySword = legacySword;
    }

    public string Name => _legacySword.SwordName;

    /// <summary>新インターフェースの呼び出しを旧メソッドに変換する</summary>
    public void Attack(string targetName)
    {
        // 旧メソッドの引数形式に合わせて変換して呼び出す
        _legacySword.Slash(targetName, _legacySword.Damage);
    }
}

4. クライアントコード

C#
class AdapterSample
{
    static void Main()
    {
        // 旧システムの剣
        var legacySword = new LegacySword("古の剣", 50);

        // アダプターで新インターフェースに適合させる
        IWeapon weapon = new LegacySwordAdapter(legacySword);

        // クライアントは IWeapon 経由で使うだけ(旧システムを意識しない)
        Console.WriteLine($"装備武器: {weapon.Name}");
        weapon.Attack("スライム");
        // => 装備武器: 古の剣
        // => [旧システム] 古の剣 で スライム に 50 ダメージ与えた!
    }
}

使いどころ

  • 既存クラスを改変せずに新しいインターフェースに対応させたいとき
  • サードパーティライブラリや旧システムを新しい設計に組み込みたいとき
  • テスト用のスタブ・モックを実装する際にインターフェースを揃えたいとき

長所・短所

✅ 長所

  • 既存クラスを変更せずに再利用できる(開放閉鎖の原則)
  • クライアントは具体的なクラスを意識せずにインターフェース経由で扱える
  • レガシーコードと新設計の橋渡しができる

⚠️ 短所

  • アダプタークラスが増えると、管理するクラス数が増える
  • 変換処理が複雑な場合、アダプターのコードが肥大化する
  • 本来なら設計時点でインターフェースを統一するほうが望ましい

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

Decorator との違い

Decorator も既存クラスをラップする構造ですが、目的が異なります。

パターン目的
Adapterインターフェースの変換(互換性のないものをつなぐ)
Decorator機能の追加(既存の振る舞いを拡張する)

Facade との違い

Facade は複数のクラスをまとめてシンプルなインターフェースを提供します。 Adapter は1つの既存クラスを別のインターフェースに変換します。

Proxy との違い

Proxy も対象クラスをラップしますが、アクセス制御・キャッシュ・遅延ロードなどが目的です。 Adapter はインターフェース変換が目的です。

まとめ

  • Adapter パターンは、互換性のないインターフェースを中間クラスで変換する
  • 既存クラスを改変せずに新しいインターフェースへ対応できる
  • ゲーム開発では旧システムの資産を新戦闘システムに組み込む場面で有効
  • Decorator や Proxy も「ラップ」の構造だが、目的が異なる
  • サードパーティ製ライブラリを自分のインターフェースに合わせる際にも活躍する
タイトルとURLをコピーしました