デコレーター(Decorator)とは?

Decoratorパターンとは、既存のオブジェクトに対して、元の構造を変更せずに新しい機能を動的に追加するための

デザインパターンです。

「装飾」という名前の由来

このパターンが「装飾」と呼ばれる理由は、まさにクリスマスツリーの装飾と同じ考え方だからです。

基本のツリー(元のオブジェクト)はそのままに、オーナメントやライト(機能)を自由に追加・取り外しできます。

元のオブジェクトを「装飾」するように、新しい振る舞いを重ね合わせていくのが特徴です。

継承との違い

従来の継承を使った機能拡張では、以下のような問題があります:

継承の問題点:

  • クラスが増えすぎる(組み合わせ爆発)
  • コンパイル時に機能が固定される
  • 複数の機能を同時に適用するのが困難

Decoratorパターンの利点:

  • 実行時に機能を追加・削除できる
  • 複数のデコレータを自由に組み合わせ可能
  • 既存コードの変更が不要
  • 機能の組み合わせ順序も制御できる

基本構造

Decoratorパターンは以下の要素で構成されます:

  1. Component(コンポーネント): 機能を定義するインターフェース
  2. ConcreteComponent(具体コンポーネント): 基本機能を実装するクラス
  3. Decorator(デコレータ基底クラス): 装飾機能の基底となるクラス
  4. ConcreteDecorator(具体デコレータ): 実際の装飾機能を実装するクラス

使用する場面

以下のような状況でDecoratorパターンが有効です:

  • 動的な機能追加が必要: 実行時に機能のON/OFFを切り替えたい
  • 機能の組み合わせ: 複数の効果を重ね合わせたい(攻撃力UP + 防御力UP など)
  • 既存コードの保護: 動作中のコードを変更せずに機能を拡張したい
  • 設定による制御: 設定ファイルやユーザー選択により機能を変更したい

RPGのようなパラメータ計算の例

RPGのようなゲームの場合、キャラクターのレベルに応じたステータスがあり、

  • 武器によって攻撃力がアップ
  • 防具によって防御力がアップ
  • 魔法によって素早さがアップ
  • 敵のデバフ効果によって防御力がダウン

といった変化が起きます。

ステータスを表現するクラスとステータスを取得するインターフェイスを用意しました。

// ユニットの基本ステータス
public class UnitParameter
{
    public float Attack = 100f;
    public float Defense = 50f;
    public float Speed = 10f;
}

// ステータス取得用インターフェイス
public interface IUnitStatus
{
    float GetAttack();
    float GetDefense();
    float GetSpeed();
}

デコレータの基底クラスと各パラメータを上昇させるデコレータクラスを用意しました。

// 基本ユニットクラス
public class Unit : IUnitStatus
{
    private UnitParameter _parameter = new UnitParameter();
    
    public float GetAttack()
    {
        return _parameter.Attack;
    }

    public float GetDefense()
    {
        return _parameter.Defense;
    }

    public float GetSpeed()
    {
        return _parameter.Speed;
    }
}

// デコレータの基底クラス
public class UnitDecoratorBase : IUnitStatus
{
    protected IUnitStatus _unitStatus;

    public UnitDecoratorBase(IUnitStatus unitStatus)
    {
        _unitStatus = unitStatus;
    }

    public virtual float GetAttack()
    {
        return _unitStatus.GetAttack();
    }

    public virtual float GetDefense()
    {
        return _unitStatus.GetDefense();
    }

    public virtual float GetSpeed()
    {
        return _unitStatus.GetSpeed();
    }
}

// 攻撃力アップデコレータ
public class AttackUpDecorator : UnitDecoratorBase
{
    private float _attack;
    
    public AttackUpDecorator(IUnitStatus unitStatus, float value)
        : base(unitStatus)
    {
        _attack = value;
    }

    public override float GetAttack()
    {
        return base.GetAttack() + _attack;
    }
}

// 防御力アップデコレータ
public class DefenseUpDecorator : UnitDecoratorBase
{
    private float _defense;
    
    public DefenseUpDecorator(IUnitStatus unitStatus, float value)
        : base(unitStatus)
    {
        _defense = value;
    }

    public override float GetDefense()
    {
        return base.GetDefense() + _defense;
    }
}

// 速度アップデコレータ
public class SpeedUpDecorator : UnitDecoratorBase
{
    private float _speed;
    
    public SpeedUpDecorator(IUnitStatus unitStatus, float value)
        : base(unitStatus)
    {
        _speed = value;
    }

    public override float GetSpeed()
    {
        return base.GetSpeed() + _speed;
    }
}

これらを以下のように呼び出します。

IUnitStatus unit = new Unit();

Debug.Log($"Attack: {unit.GetAttack()}");
Debug.Log($"Defense: {unit.GetDefense()}");
Debug.Log($"Speed: {unit.GetSpeed()}");

unit = new AttackUpDecorator(unit, 20f);
unit = new DefenseUpDecorator(unit, 10f);
unit = new SpeedUpDecorator(unit, 5f);

Debug.Log($"Attack: {unit.GetAttack()}");
Debug.Log($"Defense: {unit.GetDefense()}");
Debug.Log($"Speed: {unit.GetSpeed()}");

初期のステータスに対して、パラメータごとにDecoratorを適用しています。

最終的にすべての効果が反映された状態になります。

Decoratorパターンを使うことで、新しい効果を追加するのに既存のコードを変更する必要はなくなります。

また効果同士の干渉も最小限で済みます。

このような仕組みはRPGに限らずいろいろなジャンルのゲームで活用できそうです。

その他

Unityのコンポーネントを拡張するのにも使えそうです。

例えばButtonコンポーネントに対して、

  • 効果音を鳴らすSoundDecorator
  • エフェクトを発生させるEffectDecorator
  • アニメーションを実行するAnimationDecorator

などを用意しボタンがクリックされた時にオブジェクトに追加されているすべてのDecoratorのイベントを発火することもできそうです。

この場合も元のButtonコンポーネントを変更する必要はありません。

まとめ

今回はデザインパターンのDecoratorパターンについて解説しました。

Decoratorパターンは既存の実装を変更することなく機能を追加できる強力な設計方法です。

Unityでの開発ではMonoBehaviourと相性が良く、ゲームオブジェクトに必要な機能を組み合わせて適用できます。

継承の代わりにコンポジション(再帰的な構造)を使うことで、柔軟で保守しやすいシステムを構築できます。

例に挙げたステータス計算などに活用してみてください。

📣おしらせ!

Unity Asset Storeで BLACK FRIDAY セール が開催中です。

300以上の人気アセットを50%オフで購入するチャンス。

フラッシュセールでは最大95%オフも?

セールは 2025年12月11日午前1時(日本時間)に終了します。

🔗関連ページ