Composite

コンポジットパターンは、クライアントが個々のオブジェクトとオブジェクトのコンポジションを均一に扱えるようにする構造的なデザインパターンです。このパターンは、部分-全体の階層を表現するために使われ、クライアントに対して単一オブジェクトと複合オブジェクトを同一の方法で扱うことができる透過的な方法を提供します。

使用シーン

コンポジットパターンは以下のような場合に特に有用です:

  1. 部分-全体の階層構造を表現したい場合

    • たとえば、ファイルとフォルダの階層やUIコンポーネントのツリー構造などが該当します。

  2. クライアントが個々のオブジェクトとオブジェクトの組み合わせを同じように扱いたい場合

    • オブジェクトの集合に対しても、個々のオブジェクトに対しても、同じ操作を透過的に適用したいときに役立ちます。

メリットとデメリット

メリット

  • 一貫性のあるインターフェース:コンポジットとリーフオブジェクトを同じインターフェースで扱うことで、クライアントは操作の一貫性を保つことができます。

  • 柔軟性:新しい種類のコンポーネントを容易に追加できます。

デメリット

  • デザインの複雑性:コンポジットを設計する際には、コンポーネント間の関係を慎重に計画する必要があります。

  • オーバーヘッドの増加:簡単な構成のためにコンポジットパターンを使用すると、不要なオーバーヘッドが生じることがあります。

サンプルコード(Python)

以下は、グラフィックオブジェクトを表現するためのコンポジットパターンの実装例です。

from abc import ABC, abstractmethod

class Graphic(ABC):
    """グラフィックオブジェクトの共通インターフェース"""
    
    @abstractmethod
    def draw(self):
        pass

class CompositeGraphic(Graphic):
    """コンポジットクラス。他のグラフィックオブジェクトを子として持つことができる。"""
    
    def __init__(self):
        self.children = []
    
    def add(self, graphic):
        self.children.append(graphic)
    
    def remove(self, graphic):
        self.children.remove(graphic)
    
    def draw(self):
        for graphic in self.children:
            graphic.draw()

class Circle(Graphic):
    """単純な円を表現するクラス"""
    
    def draw(self):
        print("Circle drawn")

class Square(Graphic):
    """単純な四角形を表現するクラス"""
    
    def draw(self):
        print("Square drawn")

# クライアントコード
composite = CompositeGraphic()
composite.add(Circle())
composite.add(Square())

composite.draw()  # すべての子グラフィックオブジェクトを描画

このコードでは、CompositeGraphic クラスは複数の Graphic オブジェクトを保持し、それらの draw メソッドを一括で呼び出すことができます。CircleSquare は単純なグラフィックオブジェクトを表し、同じインターフェースを提供します。これにより、コンポジットオブジェクトと個々のオブジェクトをクライアントは同じように扱うことができます。

このパターンは、特にグラフィカルユーザーインターフェース(GUI)やファイルシステムの設計に有用で、大規模な構造を一元管理する場合に強力な手段を提供します。

ここでは、コンポジットパターンを使ったメニューシステムの実装例を提供します。このシナリオでは、異なるメニュー項目を一つのメニューに組み合わせることができ、それらがさらにサブメニューを持つことができます。

シナリオ: メニューシステム

サンプルコード(Python)

class MenuComponent(ABC):
    """メニューコンポーネントの抽象基底クラス"""

    @abstractmethod
    def add(self, menu_component):
        raise NotImplementedError

    @abstractmethod
    def remove(self, menu_component):
        raise NotImplementedError

    @abstractmethod
    def get_name(self):
        raise NotImplementedError

    @abstractmethod
    def display(self):
        raise NotImplementedError

class MenuItem(MenuComponent):
    """単一のメニュー項目を表すクラス"""
    
    def __init__(self, name):
        self.name = name

    def add(self, menu_component):
        raise NotImplementedError("Leaf nodes can't add other components.")

    def remove(self, menu_component):
        raise NotImplementedError("Leaf nodes don't contain other components.")

    def get_name(self):
        return self.name

    def display(self):
        print(f"Item: {self.get_name()}")

class Menu(MenuComponent):
    """複合メニューコンポーネントを表すクラス"""
    
    def __init__(self, name):
        self.name = name
        self.children = []

    def add(self, menu_component):
        self.children.append(menu_component)

    def remove(self, menu_component):
        self.children.remove(menu_component)

    def get_name(self):
        return self.name

    def display(self):
        print(f"Menu: {self.get_name()}")
        for component in self.children:
            component.display()

# クライアントコード
main_menu = Menu("Main Menu")
file_menu = Menu("File")
edit_menu = Menu("Edit")

new_file = MenuItem("New")
open_file = MenuItem("Open")
save_file = MenuItem("Save")
copy = MenuItem("Copy")
paste = MenuItem("Paste")

file_menu.add(new_file)
file_menu.add(open_file)
file_menu.add(save_file)

edit_menu.add(copy)
edit_menu.add(paste)

main_menu.add(file_menu)
main_menu.add(edit_menu)

main_menu.display()

このコードでは、MenuComponent クラスが全てのメニューコンポーネントの共通インターフェースを定義しており、Menu クラスはこのインターフェースを実装してコンポジットオブジェクトの役割を担います。それに対し、MenuItem クラスはリーフノードを表し、個別のメニュー項目に対応します。Menu クラスは子コンポーネントの集合を管理し、display メソッドを呼び出すことでそれ自身とすべての子コンポーネントを表示します。

このようにコンポジットパターンを使用することで、複雑なメニュー階層を簡単かつ柔軟に管理できます。クライアントコードは、メニュー項目とサブメニューを区別せずに同じ方法で扱うことができ、メニュー構造の変更や拡張が容易になります。

この記事が気に入ったらサポートをしてみませんか?