KnowHow

技術的なメモを中心にまとめます。
検索にて調べることができます。

Python基礎 オブジェクト指向13(Composite) 構造に関するデザインパターン

登録日 :2025/10/10 06:19
カテゴリ :Python基礎

Composite

ツリー構造を持つデータに再起的な処理を行えるようにするパターン

  • ツリー構造:一つの要素に複数の子要素を持ち、枝分かれしていく構造
  • 任意の枝や末端の葉に対して、共通の手順でアクセスするためのAPIを提供する

構成要素

Component

  • 抽象クラスもしくはインターフェース
  • クライアントからアクセスさせるためのAPIを定義

Leaf

  • Componentを継承(実装)する子クラス
  • ツリー構造の末端である葉に相当するクラス
  • 子に相当するオブジェクトは持たない

Composite

  • Componentを継承(実装)する子クラス
  • ツリー構造の中で任意の枝に相当するクラス
  • 属性に子に相当するオブジェクトを保持
  • Componentクラスのメソッド実装に加えて、子要素を追加・削除するためのメソッドも定義する

オブジェクト指向的要素

「ポリモーフィズム」を利用したパターン

  • Componentでクライアントに対してアクセスさせるための共通APIを定義
  • クライアントは共通APIのみを使用することで、CompositeクラスやLeafクラスを意識する必要がなく同一のものとして扱える
  • Compositeクラスは子クラスに相当するComponent型のオブジェクトを保持している
  • Compositeクラスは子クラスの共通APIのみを使用することで、子クラスが枝なのか葉なのかを意識する必要がなくなる

メリット・デメリット

メリット

  • 複雑なツリー構造を簡単に扱うことができる
     (if elseなどによる分岐を減らすことができる)

  • 新しい枝葉を簡単に追加できる

デメリット

  • 枝と葉の機能が大きく異なる場合は、共通のAPIを作ることが困難

使い所

再起的なツリー構造を実装する場合

  • ディレクトリツリー、組織階層、DOMツリーなど

サンプルコード

例)ディレクトリツリー

from abc import ABCMeta, abstractmethod
from typing import List


class Entry(metaclass=ABCMeta):
    def __init__(self, _name: str):
        self.__name = _name

    @property
    def name(self) -> str:
        return self.__name

    @abstractmethod
    def get_size(self) -> int:
        pass

    @abstractmethod
    def remove(self):
        pass


class File(Entry):
    """ Leaf class"""
    def __init__(self, _name: str, _size: int):
        super().__init__(_name)
        self.__size = _size

    def get_size(self) -> int:
        return self.__size

    def remove(self):
        print(f'Remove:{self.name}')


class Directory(Entry):
    def __init__(self, _name: str):
        super().__init__(_name)
        self.__children: List[Entry] = []

    def get_size(self) -> int:
        size = 0
        for child in self.__children:
            size += child.get_size()
        return size

    def remove(self):
        for child in self.__children:
            child.remove()
        print(f'Remove : {self.name}')

    def add(self, _child: Entry):
        self.__children.append(_child)


def client(_entry: Entry):
    print(_entry.name)
    print(_entry.get_size())
    _entry.remove()


if __name__ == '__main__':
    dir1 = Directory('design_pattern1')
    dir2 = Directory('Composite')
    file1 = File('composite.py', 100)
    file2 = File('practice.png', 200)

    dir2.add(file1)
    dir2.add(file2)
    dir1.add(dir2)

    client(dir1)