2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DecoratorパターンとCompositeパターンの組み合わせによる拡張可能なドキュメント処理システムの実装

Posted at

はじめに

デザインパターンを正しく理解し、適切に組み合わせることで、柔軟で保守性の高いシステムを構築することができます。本記事では、「Decoratorパターン」と「Compositeパターン」の本質的な特徴を理解し、それらを組み合わせた実践的な実装例を紹介します。

image.png

1. 各パターンの本質

Compositeパターン

  • 目的: 部分-全体の階層構造をツリー構造で表現し、個々のオブジェクトと複合オブジェクトを同一視して扱う
  • 特徴:
    • 単一のオブジェクト(リーフ)と複合オブジェクト(コンポジット)が同じインターフェースを実装
    • クライアントは個々のオブジェクトと複合オブジェクトを区別せずに扱える
    • 再帰的な構造を自然に表現できる

Decoratorパターン

  • 目的: オブジェクトに動的に新しい責務を追加する
  • 特徴:
    • デコレータと被装飾オブジェクトが同じインターフェースを実装
    • デコレータは被装飾オブジェクトを内包
    • デコレータを連鎖させることで、複数の機能を組み合わせられる

2. 実装例:ドキュメント処理システム

以下に、XMLドキュメントの処理を例とした実装を示します。この例では、個々の処理(リーフ)と複合処理(コンポジット)を同一視しながら、各処理に動的に機能を追加できる構造を実現します。

from abc import ABC, abstractmethod
from typing import List
from xml.etree import ElementTree as ET
from copy import deepcopy

# 基本コンポーネントインターフェース
class DocumentProcessor(ABC):
    @abstractmethod
    def process(self, element: ET.Element) -> ET.Element:
        """ドキュメント要素を処理する"""
        pass

    def clone_element(self, element: ET.Element) -> ET.Element:
        """要素の安全なコピーを作成"""
        return deepcopy(element)

# リーフ:基本的な処理を行うクラス
class LeafProcessor(DocumentProcessor):
    def process(self, element: ET.Element) -> ET.Element:
        return self.clone_element(element)

# Composite:複数の処理を組み合わせるクラス
class CompositeProcessor(DocumentProcessor):
    def __init__(self):
        self._children: List[DocumentProcessor] = []
    
    def add(self, processor: DocumentProcessor) -> None:
        """子プロセッサを追加"""
        self._children.append(processor)
    
    def remove(self, processor: DocumentProcessor) -> None:
        """子プロセッサを削除"""
        self._children.remove(processor)
    
    def process(self, element: ET.Element) -> ET.Element:
        """子プロセッサを順番に適用"""
        result = self.clone_element(element)
        
        # 各子プロセッサを適用
        for processor in self._children:
            result = processor.process(result)
            
            # 子要素に対して再帰的に処理を適用
            for child in list(result):
                processed_child = processor.process(child)
                # 処理済みの子要素で置き換え
                idx = list(result).index(child)
                result.remove(child)
                result.insert(idx, processed_child)
        
        return result

# Decorator基底クラス
class ProcessorDecorator(DocumentProcessor):
    def __init__(self, processor: DocumentProcessor):
        self._processor = processor
    
    def process(self, element: ET.Element) -> ET.Element:
        return self._processor.process(element)

# 具体的なDecorator群
class UpperCaseTextDecorator(ProcessorDecorator):
    def process(self, element: ET.Element) -> ET.Element:
        result = super().process(element)
        if result.text:
            result.text = result.text.upper()
        return result

class AddAttributeDecorator(ProcessorDecorator):
    def __init__(self, processor: DocumentProcessor, attr_name: str, attr_value: str):
        super().__init__(processor)
        self._attr_name = attr_name
        self._attr_value = attr_value
    
    def process(self, element: ET.Element) -> ET.Element:
        result = super().process(element)
        result.set(self._attr_name, self._attr_value)
        return result

class RenameTagDecorator(ProcessorDecorator):
    def __init__(self, processor: DocumentProcessor, new_tag: str):
        super().__init__(processor)
        self._new_tag = new_tag
    
    def process(self, element: ET.Element) -> ET.Element:
        result = super().process(element)
        if result.tag == "name":  # 特定のタグのみ変更
            result.tag = self._new_tag
        return result

def main():
    # サンプルXMLの作成
    xml_str = """
    <document>
        <section>
            <name>Introduction</name>
            <content>This is the introduction.</content>
        </section>
        <section>
            <name>Main Content</name>
            <content>This is the main content.</content>
            <subsection>
                <name>Details</name>
                <content>Detailed information here.</content>
            </subsection>
        </section>
    </document>
    """
    root = ET.fromstring(xml_str)

    # 基本プロセッサ(リーフ)
    base_processor = LeafProcessor()

    # デコレータの連鎖
    # 1. nameタグの処理用デコレータチェーン
    name_processor = UpperCaseTextDecorator(
        AddAttributeDecorator(
            RenameTagDecorator(base_processor, "heading"),
            "processed", "true"
        )
    )

    # 2. contentタグの処理用デコレータチェーン
    content_processor = AddAttributeDecorator(
        UpperCaseTextDecorator(base_processor),
        "type", "content"
    )

    # Compositeの作成と設定
    composite = CompositeProcessor()
    composite.add(name_processor)
    composite.add(content_processor)

    # 処理の実行と結果の表示
    print("=== 処理前のXML ===")
    ET.dump(root)

    processed_root = composite.process(root)

    print("\n=== 処理後のXML ===")
    ET.dump(processed_root)

if __name__ == "__main__":
    main()

3. 実装のポイント

Compositeパターンの正しい実装

  1. 階層構造の表現

    • DocumentProcessorインターフェースを共通の基底として定義
    • リーフ(LeafProcessor)とコンポジット(CompositeProcessor)が同じインターフェースを実装
  2. 再帰的な処理

    • コンポジットは子要素に対して再帰的に処理を適用
    • 要素の変更は安全に行う(deepcopy使用)

Decoratorパターンの適切な実装

  1. デコレータの連鎖

    • 各デコレータが前のデコレータをラップ
    • 処理の順序が明確
  2. 単一責任の原則

    • 各デコレータが1つの機能のみを追加
    • 機能の組み合わせは連鎖で実現

4. メリット・デメリット

メリット

  1. 高い柔軟性

    • 処理の追加・削除が容易
    • 処理の組み合わせが自由
  2. 保守性

    • 単一責任の原則に従った設計
    • テストが書きやすい
  3. 拡張性

    • 新しい処理の追加が既存コードに影響を与えない

デメリット

  1. 複雑性

    • パターンの正しい理解が必要
    • デバッグが難しくなる可能性
  2. パフォーマンス

    • オブジェクトの数が増える
    • 深い階層での再帰処理のコスト

5. 使い所

このパターンの組み合わせは、以下のような場面で効果を発揮します:

  1. ドキュメント処理

    • XML/HTML変換
    • テキスト整形
  2. グラフィックス処理

    • 画像フィルタの適用
    • 図形の描画処理
  3. ビルドシステム

    • ビルドステップの組み合わせ
    • 条件付きビルド処理

6. 実行結果例

実装例の実行結果を具体的に見てみましょう:

=== 処理前のXML ===
<document>
    <section>
        <name>Introduction</name>
        <content>This is the introduction.</content>
    </section>
    <section>
        <name>Main Content</name>
        <content>This is the main content.</content>
        <subsection>
            <name>Details</name>
            <content>Detailed information here.</content>
        </subsection>
    </section>
</document>

=== 処理後のXML ===
<document>
    <section>
        <heading processed="true">INTRODUCTION</heading>
        <content type="content">THIS IS THE INTRODUCTION.</content>
    </section>
    <section>
        <heading processed="true">MAIN CONTENT</heading>
        <content type="content">THIS IS THE MAIN CONTENT.</content>
        <subsection>
            <heading processed="true">DETAILS</heading>
            <content type="content">DETAILED INFORMATION HERE.</content>
        </subsection>
    </section>
</document>

変換の主なポイント:

  1. nameタグがheadingに変更され、processed="true"属性が追加
  2. すべてのテキストが大文字に変換
  3. contentタグにtype="content"属性が追加
  4. 階層構造が保持されながら、すべての要素に一貫した変換が適用

まとめ

image.png

CompositeパターンとDecoratorパターンの組み合わせは、以下の点に注意して実装することで、その真価を発揮できます:

  1. パターンの本質的な特徴を理解する

    • Compositeパターンのツリー構造と再帰
    • Decoratorパターンの機能追加と連鎖
  2. 適切な責務の分離

    • 各クラスの役割を明確に
    • 単一責任の原則の遵守
  3. 安全な実装

    • オブジェクトの変更は慎重に
    • 適切なエラー処理
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?