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

C#

一言で言うと…

  • 既存オブジェクトを変更せずに、ラップすることで動的に機能を追加する仕組み

概要

Decorator パターンは、GoF デザインパターンの一つで、構造に関するデザインパターンです。
別名 Wrapper とも呼ばれます。
対象オブジェクトと同じインターフェースを持つ「デコレーター」でラップすることで、 既存クラスを変更せずに機能を追加したり組み合わせたりできます。

継承と違い、デコレーターを複数重ねることで実行時に柔軟に機能を組み合わせられるのが特徴です。

ゲーム開発では、武器に炎・氷・毒などの属性を動的に付与する場面に適しています。

構成

classDiagram
    class IComponent {
        << interface >>
        + Operation() string
    }
    class ConcreteComponent {
        + Operation() string
    }
    class BaseDecorator {
        # _component : IComponent
        + Operation() string
    }
    class ConcreteDecoratorA {
        + Operation() string
    }
    class ConcreteDecoratorB {
        + Operation() string
    }
    ConcreteComponent ..|> IComponent : implements
    BaseDecorator ..|> IComponent : implements
    BaseDecorator o-- IComponent : wraps
    ConcreteDecoratorA --|> BaseDecorator : extends
    ConcreteDecoratorB --|> BaseDecorator : extends
    Client ..> IComponent : << use >>
要素役割
IComponent基本オブジェクトとデコレーターを統一するインターフェース
ConcreteComponentデコレートされる元のオブジェクト
BaseDecoratorIComponent を保持し、処理を委譲する基底デコレーター
ConcreteDecorator具体的な機能を追加するデコレーター

実装例(C#)

クラス概要

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

利用ケースは以下のように、基本の剣に炎・毒などの属性エンチャントを動的に重ねて付与する機構です。

  • IWeapon:武器の共通インターフェース(IComponent)
  • Sword:素の剣(ConcreteComponent)
  • WeaponDecorator:デコレーターの基底クラス(BaseDecorator)
  • FlameDecoratorPoisonDecorator:属性エンチャント(ConcreteDecorator)

ソースコード

1. 共通インターフェース(IComponent)

C#
/// <summary>
/// 武器インターフェース (IComponent)
/// </summary>
public interface IWeapon
{
    string GetDescription();
    int GetDamage();
}

2. 素の剣(ConcreteComponent)

C#
/// <summary>
/// 素の剣 (Concrete Component)
/// </summary>
public class Sword : IWeapon
{
    public string GetDescription() => "鉄の剣";
    public int GetDamage()         => 30;
}

3. デコレーター基底クラス(BaseDecorator)

C#
/// <summary>
/// 武器デコレーター基底クラス (Base Decorator)
/// </summary>
public abstract class WeaponDecorator : IWeapon
{
    protected readonly IWeapon _weapon;

    protected WeaponDecorator(IWeapon weapon)
    {
        _weapon = weapon;
    }

    public virtual string GetDescription() => _weapon.GetDescription();
    public virtual int GetDamage()         => _weapon.GetDamage();
}

4. 具体デコレーター(ConcreteDecorator)

C#
/// <summary>
/// 炎エンチャント (Concrete Decorator A)
/// </summary>
public class FlameDecorator : WeaponDecorator
{
    public FlameDecorator(IWeapon weapon) : base(weapon) { }

    public override string GetDescription() => _weapon.GetDescription() + " + 炎エンチャント";
    public override int GetDamage()         => _weapon.GetDamage() + 20;  // 炎ダメージ追加
}

/// <summary>
/// 毒エンチャント (Concrete Decorator B)
/// </summary>
public class PoisonDecorator : WeaponDecorator
{
    public PoisonDecorator(IWeapon weapon) : base(weapon) { }

    public override string GetDescription() => _weapon.GetDescription() + " + 毒エンチャント";
    public override int GetDamage()         => _weapon.GetDamage() + 10;  // 毒ダメージ追加
}

5. クライアントコード

C#
using System;

class DecoratorSample
{
    static void Main()
    {
        // 素の剣
        IWeapon sword = new Sword();
        Console.WriteLine($"{sword.GetDescription()} / ダメージ:{sword.GetDamage()}");
        // => 鉄の剣 / ダメージ:30

        // 炎エンチャントを付与
        IWeapon flameSword = new FlameDecorator(sword);
        Console.WriteLine($"{flameSword.GetDescription()} / ダメージ:{flameSword.GetDamage()}");
        // => 鉄の剣 + 炎エンチャント / ダメージ:50

        // さらに毒エンチャントを重ねる
        IWeapon flamePoison = new PoisonDecorator(flameSword);
        Console.WriteLine($"{flamePoison.GetDescription()} / ダメージ:{flamePoison.GetDamage()}");
        // => 鉄の剣 + 炎エンチャント + 毒エンチャント / ダメージ:60
    }
}

デコレーターを重ねることで、既存クラスを変更せずに機能を積み重ねられます。

使いどころ

  • 継承を使わずに実行時に機能を動的に追加したいとき
  • 機能の組み合わせが多岐にわたる場合(炎×毒×氷など)
  • 既存クラスを変更せずに機能を拡張したいとき(開放閉鎖の原則)

長所・短所

✅ 長所

  • 既存クラスを変更せずに機能を追加できる(開放閉鎖の原則)
  • デコレーターを自由に重ねて組み合わせられる
  • サブクラスを爆発的に増やすことなく多様な組み合わせを表現できる

⚠️ 短所

  • デコレーターを多重に重ねると、デバッグ時に動作を追いにくくなる
  • 設定順序が結果に影響するため、利用者が組み合わせの意味を理解する必要がある
  • 単純なケースでは継承より複雑に感じる

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

Adapter との違い

Adapter はインターフェースを変換することが目的です。

Decorator は同じインターフェースを保ちながら機能を追加することが目的です。

まとめ

  • Decorator パターンは、同じインターフェースでラップして機能を動的に追加する
  • デコレーターを重ねることで、継承を使わずに多様な組み合わせを実現できる
  • ゲーム開発では武器の属性エンチャントや、キャラクターへのバフ付与に有効
  • Adapter はインターフェース変換、Decorator は機能追加という目的の違いを意識する
  • 重ねすぎるとデバッグが難しくなるため、適切な組み合わせ数に留めることが重要

タイトルとURLをコピーしました