導入
Compositeは「個と全体を同じように扱う」——フォルダとファイルを再帰的に同一視するようなツリー構造に。Decoratorは「既存オブジェクトを包んで機能を継ぎ足す」——継承せずに振る舞いを積み重ねます。
図解
flowchart TB
subgraph Decorator
BASE["Coffee 300円"] --> M["+Milk 50円"] --> S["+Sugar 20円 = 370円"]
end
サンプル
Compositeは「同じ形の入れ子」、Decoratorは「同じ形の包み」。どちらも共通インターフェースのファイルを起点に読むとわかりやすくなります。
1. 木のノードの共通の形(INode.cs)。ファイルもフォルダも「サイズが言える」だけの存在にします。
public interface INode
{
int Size();
}
2. 葉(FileNode.cs)と 3. 枝(Folder.cs)。枝は子を再帰的に集計します。
public class FileNode : INode
{
private readonly int _size;
public FileNode(string name, int size) => _size = size;
public int Size() => _size;
}
// Composite: 子(ファイルでもフォルダでも)を同じ INode として再帰集計
public class Folder : INode
{
private readonly INode[] _children;
public Folder(string name, INode[] children) => _children = children;
public int Size() => _children.Sum(c => c.Size()); // 子を再帰集計
}
4. 飲み物の共通の形(ICoffee.cs)と 5. 芯になる本体(PlainCoffee.cs)。
public interface ICoffee
{
string Description();
int Cost();
}
public class PlainCoffee : ICoffee
{
public string Description() => "コーヒー";
public int Cost() => 300;
}
6. 包む側(Milk.cs / Sugar.cs)。トッピング1つ=ファイル1つ。新しいトッピングはファイルを足すだけで、既存のクラスは1行も変わりません。
// Decorator: 同じ ICoffee を内側に持ち、結果に自分の分を足す
public class Milk : ICoffee
{
private readonly ICoffee _inner;
public Milk(ICoffee inner) => _inner = inner;
public string Description() => _inner.Description() + "+ミルク";
public int Cost() => _inner.Cost() + 50;
}
public class Sugar : ICoffee
{
private readonly ICoffee _inner;
public Sugar(ICoffee inner) => _inner = inner;
public string Description() => _inner.Description() + "+砂糖";
public int Cost() => _inner.Cost() + 20;
}
7. 組み立てて使う側(Program.cs)。包む順番を変えても動くことを確かめてみてください。
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
ICoffee coffee = new Sugar(new Milk(new PlainCoffee()));
Console.WriteLine($"{coffee.Description()}: {coffee.Cost()}円"); // コーヒー+ミルク+砂糖: 370円
- Composite: 個(ファイル)と全体(フォルダ)を同じインターフェースで扱い、再帰処理する
- Decorator: 対象を包む同じインターフェースのクラスで、機能を積み重ねる
- どちらも「同じインターフェースで包む/まとめる」のが鍵
- 追加は新しいファイルを1つ足すだけ。既存ファイルを触らずに機能が増える
演習
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です。