本文へスキップ
BecomeCoder

C#デザインパターンコース · 第1章 デザインパターン · レッスン6

構造②:Composite / Decorator

ブラウザで完結

導入

Compositeは「個と全体を同じように扱う」——フォルダとファイルを再帰的に同一視するようなツリー構造に。Decoratorは「既存オブジェクトを包んで機能を継ぎ足す」——継承せずに振る舞いを積み重ねます。

図解

flowchart TB
    subgraph Decorator
        BASE["Coffee 300円"] --> M["+Milk 50円"] --> S["+Sugar 20円 = 370円"]
    end

サンプル

// Composite: ファイルもフォルダも同じ INode として扱い、合計サイズを再帰計算
INode tree = new Folder("root", new INode[]
{
    new FileNode("a.txt", 100),
    new Folder("sub", new INode[] { new FileNode("b.txt", 200) }),
});
Console.WriteLine(tree.Size());   // 300

// Decorator: コーヒーを包んで機能(トッピング)を継ぎ足す
ICoffee coffee = new Sugar(new Milk(new PlainCoffee()));
Console.WriteLine($"{coffee.Description()}: {coffee.Cost()}円");   // コーヒー+ミルク+砂糖: 370円

interface INode { int Size(); }
class FileNode : INode
{
    private readonly int _size;
    public FileNode(string name, int size) => _size = size;
    public int Size() => _size;
}
class Folder : INode
{
    private readonly INode[] _children;
    public Folder(string name, INode[] children) => _children = children;
    public int Size() => _children.Sum(c => c.Size());   // 子を再帰集計
}

interface ICoffee { string Description(); int Cost(); }
class PlainCoffee : ICoffee { public string Description() => "コーヒー"; public int Cost() => 300; }
class Milk : ICoffee
{
    private readonly ICoffee _inner;
    public Milk(ICoffee inner) => _inner = inner;
    public string Description() => _inner.Description() + "+ミルク";
    public int Cost() => _inner.Cost() + 50;
}
class Sugar : ICoffee
{
    private readonly ICoffee _inner;
    public Sugar(ICoffee inner) => _inner = inner;
    public string Description() => _inner.Description() + "+砂糖";
    public int Cost() => _inner.Cost() + 20;
}
  • Composite: 個(ファイル)と全体(フォルダ)を同じインターフェースで扱い、再帰処理する
  • Decorator: 対象を包む同じインターフェースのクラスで、機能を積み重ねる
  • どちらも「同じインターフェースで包む/まとめる」のが鍵

演習

IMessage m = new Encrypted(new Plain("hello"));
Console.WriteLine(m.Content());   // [暗号化]hello

// TODO: Plain を包んで内容の先頭に "[暗号化]" を付ける Decorator の Encrypted を定義してください
interface IMessage { string Content(); }
class Plain : IMessage
{
    private readonly string _text;
    public Plain(string text) => _text = text;
    public string Content() => _text;
}
___
  • 期待される出力: [暗号化]hello
ヒント1を見る

class Encrypted : IMessage { private readonly IMessage _inner; public Encrypted(IMessage inner) => _inner = inner; public string Content() => "[暗号化]" + _inner.Content(); }

ヒント2を見る

内側のオブジェクトを保持し、その結果を加工して返します

まとめ

  • Compositeは個と全体を同一視してツリーを扱う
  • Decoratorは包んで機能を継ぎ足す(継承の代替)
  • 共通インターフェースが両者の要

次回: 窓口・共有・代理のFacade/Flyweight/Proxyです。

実際に動かしてみよう

本文のサンプルや演習のコードは、コードブロック右上の「コピー」ボタンでコピーして、下のエディタに貼り付ければそのまま実行できます。

C# — ブラウザ内で実行

ブラウザ内でC#を動かす環境を読み込みます(初回のみ数秒)。
スクロールして表示された時点でも自動で読み込まれます。