はじめに
複雑な階層構造を持つオブジェクトの管理は、多くのシステム開発で直面する課題です。この記事では、CompositeパターンとFactory Methodパターンを組み合わせて、Pythonでこの課題を解決する方法を解説します。
なぜこの組み合わせが必要なのか?
課題
- 複雑な階層構造を持つオブジェクトの管理が煩雑
- オブジェクトの生成ロジックが複雑化
- コードの保守性と拡張性の確保が困難
解決策
- Compositeパターン:階層構造の統一的な扱い
- Factory Methodパターン:オブジェクト生成の抽象化
- 両者の組み合わせ:柔軟で保守性の高い設計の実現
メリット
-
統一的なインターフェース
- すべてのコンポーネントを同じように扱える
- クライアントコードがシンプルになる
-
拡張性の向上
- 新しい種類のコンポーネントを追加しやすい
- 生成ロジックの変更が容易
-
カプセル化の促進
- オブジェクト生成の詳細をクライアントから隠蔽
- 階層構造の内部実装を隠蔽
デメリット
-
設計の複雑化
- 単純な階層構造には過剰な場合がある
- 初期設計に時間がかかる
-
パフォーマンスへの影響
- 抽象クラスや継承による若干のオーバーヘッド
- 深い階層での再帰処理のコスト
実装例
from abc import ABC, abstractmethod
from typing import List, Union
class FileSystemComponent(ABC):
def __init__(self, name: str):
self.name = name
@abstractmethod
def get_size(self) -> int:
pass
@abstractmethod
def print_structure(self, indent: str = "") -> None:
pass
class File(FileSystemComponent):
def __init__(self, name: str, size: int):
super().__init__(name)
if size < 0:
raise ValueError("File size cannot be negative")
self.size = size
def get_size(self) -> int:
return self.size
def print_structure(self, indent: str = "") -> None:
print(f"{indent}- {self.name} ({self.size} bytes)")
class Directory(FileSystemComponent):
def __init__(self, name: str):
super().__init__(name)
self.children: List[FileSystemComponent] = []
def add(self, component: FileSystemComponent) -> None:
# 型チェック
if not isinstance(component, FileSystemComponent):
raise TypeError("component must be a FileSystemComponent")
# 循環参照のチェック
if component is self:
raise ValueError("Cannot add directory to itself")
# 親ディレクトリへの参照チェック(再帰的)
if isinstance(component, Directory):
current = self
while hasattr(current, '_parent'):
if current._parent is component:
raise ValueError("Cannot create circular reference in directory structure")
current = current._parent
# 親への参照を設定
if hasattr(component, '_parent'):
raise ValueError("Component already has a parent")
component._parent = self
self.children.append(component)
def remove(self, component: FileSystemComponent) -> None:
if component in self.children:
delattr(component, '_parent')
self.children.remove(component)
else:
raise ValueError("Component not found in this directory")
def get_size(self) -> int:
return sum(child.get_size() for child in self.children)
def print_structure(self, indent: str = "") -> None:
print(f"{indent}+ {self.name} ({self.get_size()} bytes)")
for child in self.children:
child.print_structure(indent + " ")
class FileSystemFactory(ABC):
@abstractmethod
def create_file(self, name: str, size: int) -> File:
pass
@abstractmethod
def create_directory(self, name: str) -> Directory:
pass
class StandardFileSystemFactory(FileSystemFactory):
def create_file(self, name: str, size: int) -> File:
if not name:
raise ValueError("File name cannot be empty")
return File(name, size)
def create_directory(self, name: str) -> Directory:
if not name:
raise ValueError("Directory name cannot be empty")
return Directory(name)
def create_sample_structure() -> Directory:
try:
factory = StandardFileSystemFactory()
# ルートディレクトリの作成
root = factory.create_directory("root")
# ドキュメント用ディレクトリ
docs = factory.create_directory("documents")
docs.add(factory.create_file("resume.doc", 1000))
docs.add(factory.create_file("report.pdf", 2000))
# 画像用ディレクトリ
pics = factory.create_directory("pictures")
pics.add(factory.create_file("vacation.jpg", 3000))
pics.add(factory.create_file("family.jpg", 4000))
# 構造の組み立て
root.add(docs)
root.add(pics)
root.add(factory.create_file("notes.txt", 500))
return root
except (ValueError, TypeError) as e:
print(f"Error creating file system structure: {e}")
raise
def main() -> None:
try:
# サンプル構造の作成と表示
root = create_sample_structure()
print("ファイルシステム構造:")
root.print_structure()
# エラーケースのテスト
print("\nエラーケースのテスト:")
try:
root.add(root) # 循環参照のテスト
except ValueError as e:
print(f"Expected error: {e}")
try:
root.add("not a component") # 型チェックのテスト
except TypeError as e:
print(f"Expected error: {e}")
except Exception as e:
print(f"Unexpected error occurred: {e}")
if __name__ == "__main__":
main()
主要な実装ポイント
-
型ヒントの活用
- 具体的な戻り値の型(
File
,Directory
)を明示 - メソッドの戻り値に
None
を明示 - コレクションの型ヒントを詳細化
- 具体的な戻り値の型(
-
エラー処理の実装
- ファイル名の空チェック
- ファイルサイズの負値チェック
- コンポーネントの型チェック
- 例外処理の階層化
-
循環参照の防止
- 自身への参照チェック
- 親ディレクトリへの循環参照チェック
- 親子関係の管理機能
使用シーン
このパターンの組み合わせは、以下のような状況で特に効果的です:
-
複雑な階層構造を持つシステム
- ファイルシステムのエミュレーション
- XML/HTMLドキュメントの構造表現
- 組織図や部門構造の管理
-
動的に構造が変化するシステム
- プラグインシステム
- カスタマイズ可能なメニュー構造
- 動的なUIコンポーネント管理
実行結果
このコードを実行すると、以下のような出力が得られます:
ファイルシステム構造:
+ root (10500 bytes)
+ documents (3000 bytes)
- resume.doc (1000 bytes)
- report.pdf (2000 bytes)
+ pictures (7000 bytes)
- vacation.jpg (3000 bytes)
- family.jpg (4000 bytes)
- notes.txt (500 bytes)
エラーケースのテスト:
Expected error: Cannot add directory to itself
Expected error: component must be a FileSystemComponent
まとめ
CompositeパターンとFactory Methodパターンの組み合わせは、複雑な階層構造の管理に強力なソリューションを提供します。Pythonの型ヒントや例外処理を活用することで、より安全で保守性の高いコードを実現できます。
ただし、システムの要件と規模を考慮し、この組み合わせが本当に必要かどうかを慎重に判断することが重要です。シンプルなシステムでは、より簡単な設計で十分な場合もあります。