1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

全30回:静的と動的でどう違うのか、JavaとPythonで学ぶデザインパターン - Day 5 Factory Methodパターン:オブジェクト生成を柔軟にする

Posted at

はじめに

皆さん、こんにちは!「JavaとPythonで比べるデザインパターン」シリーズの第5回目です。
今回は、オブジェクトの生成処理をサブクラスに任せることで、コードを柔軟にするFactory Method(ファクトリーメソッド)パターンについて解説します。


Factory Methodパターンとは?

Factory Methodパターンは、オブジェクトの生成処理を抽象化し、どのクラスをインスタンス化するかをサブクラスに決定させるためのデザインパターンです。

なぜこのパターンが重要なのでしょうか?

従来のコードでは、以下のように直接的にオブジェクトを生成していました:

// 問題のあるコード例
public class DocumentProcessor {
    public void processDocument(String type) {
        Document doc;
        if (type.equals("pdf")) {
            doc = new PDFDocument();  // 具象クラスに直接依存
        } else if (type.equals("word")) {
            doc = new WordDocument(); // 具象クラスに直接依存
        }
        // 新しい形式を追加するたびに、このメソッドを修正する必要がある
        doc.process();
    }
}

このアプローチの問題点:

  • Open-Closed原則違反: 新しいドキュメント形式を追加するたびに既存コードを修正
  • 依存関係の結合: クライアントコードが具象クラスに強く依存
  • テストの困難さ: モックオブジェクトに差し替えにくい

Factory Methodパターンを使うことで、これらの問題を解決できます。


Javaでの実装:厳格な抽象化

Javaでは、インターフェースと抽象クラスを使って、Factory Methodパターンを厳密に実装します。より実践的なドキュメント処理システムを例に見てみましょう。

// 1. 製品インターフェース
interface Document {
    void open();
    void save();
    String getFormat();
}

// 2. 具象製品クラス
class PDFDocument implements Document {
    @Override
    public void open() {
        System.out.println("PDF document opened with Adobe Reader");
    }
    
    @Override
    public void save() {
        System.out.println("PDF document saved with compression");
    }
    
    @Override
    public String getFormat() {
        return "PDF";
    }
}

class WordDocument implements Document {
    @Override
    public void open() {
        System.out.println("Word document opened with Microsoft Word");
    }
    
    @Override
    public void save() {
        System.out.println("Word document saved with auto-backup");
    }
    
    @Override
    public String getFormat() {
        return "DOCX";
    }
}

class ExcelDocument implements Document {
    @Override
    public void open() {
        System.out.println("Excel document opened with Microsoft Excel");
    }
    
    @Override
    public void save() {
        System.out.println("Excel document saved with formula validation");
    }
    
    @Override
    public String getFormat() {
        return "XLSX";
    }
}

// 3. クリエイター抽象クラス
abstract class DocumentProcessor {
    // Factory Method: サブクラスで実装される
    protected abstract Document createDocument();
    
    // テンプレートメソッド: Factory Methodを使用
    public final void processDocument() {
        Document document = createDocument();
        System.out.println("Processing " + document.getFormat() + " document:");
        document.open();
        
        // 何らかの処理を実行
        System.out.println("Performing document operations...");
        
        document.save();
        System.out.println("Document processing completed.\n");
    }
}

// 4. 具象クリエイタークラス
class PDFProcessor extends DocumentProcessor {
    @Override
    protected Document createDocument() {
        return new PDFDocument();
    }
}

class WordProcessor extends DocumentProcessor {
    @Override
    protected Document createDocument() {
        return new WordDocument();
    }
}

class ExcelProcessor extends DocumentProcessor {
    @Override
    protected Document createDocument() {
        return new ExcelDocument();
    }
}

// 5. クライアントコード
public class DocumentApplication {
    public static void main(String[] args) {
        // プロセッサーの配列(新しい形式を追加しても変更不要)
        DocumentProcessor[] processors = {
            new PDFProcessor(),
            new WordProcessor(),
            new ExcelProcessor()
        };
        
        for (DocumentProcessor processor : processors) {
            processor.processDocument();
        }
    }
}

実行結果:

Processing PDF document:
PDF document opened with Adobe Reader
Performing document operations...
PDF document saved with compression
Document processing completed.

Processing DOCX document:
Word document opened with Microsoft Word
Performing document operations...
Word document saved with auto-backup
Document processing completed.

Processing XLSX document:
Excel document opened with Microsoft Excel
Performing document operations...
Excel document saved with formula validation
Document processing completed.

Javaの実装の特徴:

  • 型安全性が保証されている
  • コンパイル時にエラーを検出可能
  • IDEによる自動補完やリファクタリング支援が充実

Pythonでの実装:柔軟なアプローチ

Pythonでは、より柔軟で簡潔な方法でFactory Methodパターンを実装できます。

アプローチ1: 関数ベースのファクトリー

from abc import ABC, abstractmethod
from typing import Protocol

# 製品プロトコル(インターフェース)
class Document(Protocol):
    def open(self) -> None: ...
    def save(self) -> None: ...
    def get_format(self) -> str: ...

# 具象製品クラス
class PDFDocument:
    def open(self):
        print("PDF document opened with Adobe Reader")
    
    def save(self):
        print("PDF document saved with compression")
    
    def get_format(self):
        return "PDF"

class WordDocument:
    def open(self):
        print("Word document opened with Microsoft Word")
    
    def save(self):
        print("Word document saved with auto-backup")
    
    def get_format(self):
        return "DOCX"

class ExcelDocument:
    def open(self):
        print("Excel document opened with Microsoft Excel")
    
    def save(self):
        print("Excel document saved with formula validation")
    
    def get_format(self):
        return "XLSX"

# ファクトリー関数
def create_document(doc_type: str) -> Document:
    """ファクトリー関数:ドキュメント形式に応じてオブジェクトを生成"""
    document_classes = {
        "pdf": PDFDocument,
        "word": WordDocument,
        "excel": ExcelDocument
    }
    
    document_class = document_classes.get(doc_type.lower())
    if not document_class:
        raise ValueError(f"Unsupported document type: {doc_type}")
    
    return document_class()

# ドキュメントプロセッサー
class DocumentProcessor:
    def __init__(self, document_factory):
        self.document_factory = document_factory
    
    def process_document(self, doc_type: str):
        document = self.document_factory(doc_type)
        print(f"Processing {document.get_format()} document:")
        document.open()
        print("Performing document operations...")
        document.save()
        print("Document processing completed.\n")

# 使用例
def main():
    processor = DocumentProcessor(create_document)
    
    document_types = ["pdf", "word", "excel"]
    for doc_type in document_types:
        processor.process_document(doc_type)

if __name__ == "__main__":
    main()

アプローチ2: クラスベースのファクトリー(Javaに近いスタイル)

from abc import ABC, abstractmethod

class Document(ABC):
    @abstractmethod
    def open(self): pass
    
    @abstractmethod
    def save(self): pass
    
    @abstractmethod
    def get_format(self): pass

class PDFDocument(Document):
    def open(self):
        print("PDF document opened with Adobe Reader")
    
    def save(self):
        print("PDF document saved with compression")
    
    def get_format(self):
        return "PDF"

class WordDocument(Document):
    def open(self):
        print("Word document opened with Microsoft Word")
    
    def save(self):
        print("Word document saved with auto-backup")
    
    def get_format(self):
        return "DOCX"

class DocumentProcessor(ABC):
    @abstractmethod
    def create_document(self) -> Document:
        """Factory Method: サブクラスで実装"""
        pass
    
    def process_document(self):
        """テンプレートメソッド: Factory Methodを使用"""
        document = self.create_document()
        print(f"Processing {document.get_format()} document:")
        document.open()
        print("Performing document operations...")
        document.save()
        print("Document processing completed.\n")

class PDFProcessor(DocumentProcessor):
    def create_document(self) -> Document:
        return PDFDocument()

class WordProcessor(DocumentProcessor):
    def create_document(self) -> Document:
        return WordDocument()

# 使用例
def main():
    processors = [PDFProcessor(), WordProcessor()]
    for processor in processors:
        processor.process_document()

if __name__ == "__main__":
    main()

アプローチ3: より Pythonic なレジストリパターン

from typing import Dict, Type, Callable
import inspect

class DocumentRegistry:
    """ドキュメントクラスのレジストリ"""
    _documents: Dict[str, Type] = {}
    
    @classmethod
    def register(cls, name: str):
        """デコレータ:ドキュメントクラスを登録"""
        def decorator(document_class):
            cls._documents[name] = document_class
            return document_class
        return decorator
    
    @classmethod
    def create_document(cls, name: str):
        """登録されたドキュメントクラスからインスタンスを生成"""
        document_class = cls._documents.get(name)
        if not document_class:
            raise ValueError(f"Unknown document type: {name}")
        return document_class()
    
    @classmethod
    def list_available_types(cls):
        """利用可能なドキュメント形式を一覧表示"""
        return list(cls._documents.keys())

# ドキュメントクラスの定義と登録
@DocumentRegistry.register("pdf")
class PDFDocument:
    def open(self):
        print("PDF document opened with Adobe Reader")
    
    def save(self):
        print("PDF document saved with compression")
    
    def get_format(self):
        return "PDF"

@DocumentRegistry.register("word")
class WordDocument:
    def open(self):
        print("Word document opened with Microsoft Word")
    
    def save(self):
        print("Word document saved with auto-backup")
    
    def get_format(self):
        return "DOCX"

# 新しいドキュメント形式を追加するのも簡単
@DocumentRegistry.register("powerpoint")
class PowerPointDocument:
    def open(self):
        print("PowerPoint document opened with Microsoft PowerPoint")
    
    def save(self):
        print("PowerPoint document saved with slide transitions")
    
    def get_format(self):
        return "PPTX"

# 使用例
def main():
    print("Available document types:", DocumentRegistry.list_available_types())
    
    for doc_type in ["pdf", "word", "powerpoint"]:
        document = DocumentRegistry.create_document(doc_type)
        print(f"\nProcessing {document.get_format()} document:")
        document.open()
        document.save()

if __name__ == "__main__":
    main()

実際の使用場面

Factory Methodパターンは、以下のような場面で威力を発揮します:

1. プラグインシステム

# プラグインの動的読み込み
class PluginFactory:
    @staticmethod
    def create_plugin(plugin_name: str, config_path: str):
        # 設定ファイルに基づいてプラグインを選択・生成
        pass

2. 設定に基づくオブジェクト生成

# 環境変数やコンフィグファイルに基づく生成
class DatabaseFactory:
    @staticmethod
    def create_connection(env: str):
        if env == "production":
            return PostgreSQLConnection()
        elif env == "testing":
            return SQLiteConnection()
        else:
            return MockConnection()

3. フレームワークの拡張ポイント

# ユーザーがカスタマイズ可能な部分
class WebFramework:
    def create_response(self, request_type):
        # ユーザーが独自のレスポンスクラスを定義可能
        pass

まとめ:言語特性を活かした実装

観点 Java Python
型安全性 コンパイル時の型チェック 実行時の型チェック(型ヒント使用推奨)
実装の厳密さ インターフェース・抽象クラスによる厳密な契約 Duck Typing による柔軟な実装
拡張性 継承ベースの拡張 デコレータ・レジストリパターンによる動的拡張
コード量 やや冗長だが明確 簡潔で表現力豊か
パフォーマンス 事前最適化可能 実行時の柔軟性を重視

Factory Methodパターンの本質は「生成責任の分離」です。 クライアントコードは「何を作るか」に集中し、「どう作るか」はファクトリーに委任することで、コードの保守性と拡張性を大幅に向上させることができます。

次回は、関連するオブジェクト群をまとめて生成するAbstract Factoryパターンについて解説します。Factory Methodパターンとの違いや使い分けについても詳しく説明しますので、お楽しみに!

次回のテーマ:「Day 6 Abstract Factoryパターン:関連するオブジェクト群をまとめて生成する」

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?