コンポジットパターン:階層構造を扱う設計パターン

概要

コンポジットパターンは、オブジェクトを木構造に組み合わせることで「部分-全体」の階層構造を表現します。このパターンにより、クライアントは個々のオブジェクトと複合オブジェクトを一貫した方法で扱うことができます。

適用場面

  1. 部分-全体の階層構造が必要な場合
  2. クライアントが複合オブジェクトと個々のオブジェクトの違いを意識せず、同一のインターフェースで操作できるようにしたい場合

コード例

1. コンポーネントインターフェースの定義


/// <summary>
/// コンポジット内のオブジェクトに共通のインターフェースを定義
/// 適切な場合、すべてのクラスで共通のインターフェースのデフォルト動作を実装
/// サブコンポーネントへのアクセスと管理用のインターフェースを宣言
/// </summary>
abstract class Element
{
    protected string identifier;

    public Element(string identifier) 
    {
        this.identifier = identifier;
    }

    public abstract void Add(Element element);
    public abstract void Remove(Element element);
    public abstract void Display(int level);
}

2. リーフ(葉)クラスの実装


class Leaf : Element
{
    public Leaf(string identifier) : base(identifier) { }

    /// <summary>
    /// リーフには子要素を追加できないため、AddとRemoveメソッドの実装は意味をなさない
    /// しかし、リーフとノードの抽象レベルでの差異をなくし、完全に一貫したインターフェースを提供できる
    /// </summary>
    /// <param name="element">追加する要素</param>
    public override void Add(Element element)
    {
        Console.WriteLine("リーフには要素を追加できません");
    }

    public override void Remove(Element element)
    {
        Console.WriteLine("リーフから要素を削除できません");
    }

    /// <summary>
    /// リーフの具体的な動作として、識別子と階層レベルを表示
    /// </summary>
    /// <param name="level">表示する階層レベル</param>
    public override void Display(int level) 
    {
        Console.WriteLine(new String('-', level) + identifier);
    }
}

3. コンポジット(複合)クラスの実装


/// <summary>
/// 子要素を持つコンテナクラスを定義
/// Elementインターフェースで子要素関連の操作を実装
/// </summary>
class Composite : Element
{
    /// <summary>
    /// 子要素を格納するコレクション
    /// </summary>
    private List<Element> components = new List<Element>();
    
    public Composite(string identifier) : base(identifier) { }

    public override void Add(Element element)
    {
        components.Add(element);
    }

    public override void Remove(Element element)
    {
        components.Remove(element);
    }

    /// <summary>
    /// コンポジット自身の識別子を表示し、子要素を再帰的に表示
    /// </summary>
    /// <param name="level">表示する階層レベル</param>
    public override void Display(int level)
    {
        Console.WriteLine(new String('-', level) + identifier);
        foreach(Element component in components)
        {
            component.Display(level + 2);
        }
    }
}

4. クライアントコードによる木構造のデモンストレーション


/// <summary>
/// コンポジットパターンのテスト
/// </summary>
static void DemonstrateComposite()
{
    // ルートノードを作成し、2つのリーフを追加
    Composite root = new Composite("ルート");
    root.Add(new Leaf("リーフA"));
    root.Add(new Leaf("リーフB"));

    // ルートにXブランチを追加し、そのブランチに2つのリーフを追加
    Composite branchX = new Composite("ブランチX");
    branchX.Add(new Leaf("リーフXA"));
    branchX.Add(new Leaf("リーフXB"));
    root.Add(branchX);

    // ブランチXにXYサブブランチを追加し、そのブランチに2つのリーフを追加
    Composite branchXY = new Composite("ブランチXY");
    branchXY.Add(new Leaf("リーフXYA"));
    branchXY.Add(new Leaf("リーフXYB"));
    root.Add(branchXY);

    // ルートにさらに2つのリーフを追加し、そのうち1つを削除
    root.Add(new Leaf("リーフC"));
    Leaf leafD = new Leaf("リーフD");
    root.Add(leafD);
    root.Remove(leafD);

    // 木構造全体を表示
    root.Display(1);
    Console.Read();
}

補足

.NET FrameworkのSystem.Web.UI.ControlクラスにもAddメソッドとRemoveメソッドが存在し、コンポジットパターンの典型的な応用例と言われています。詳細な実装については、実際にソースコードを確認することをお勧めします。

タグ: コンポジットパターン オブジェクト指向設計 階層構造 GoFパターン

7月1日 18:18 投稿