工廠模式用於封裝物件的建立邏輯,讓使用者不需要知道具體的類別名稱,只需要提供識別資訊即可取得適當的物件。

為什麼需要工廠模式?

問題場景

 1# 使用者需要知道所有具體類別
 2if file_type == "json":
 3    parser = JsonParser()
 4elif file_type == "yaml":
 5    parser = YamlParser()
 6elif file_type == "xml":
 7    parser = XmlParser()
 8else:
 9    raise ValueError(f"Unknown type: {file_type}")
10
11# 問題:
12# 1. 使用者需要導入所有具體類別
13# 2. 新增類型需要修改多處程式碼
14# 3. 建立邏輯重複

使用工廠解決

1# 使用者只需要知道工廠
2parser = ParserFactory.create(file_type)
3
4# 新增類型只需要註冊到工廠
5# 使用者程式碼不需要修改

基本實作

簡單工廠

 1from abc import ABC, abstractmethod
 2
 3class BaseParser(ABC):
 4    @abstractmethod
 5    def parse(self, content: str) -> dict:
 6        pass
 7
 8class JsonParser(BaseParser):
 9    def parse(self, content: str) -> dict:
10        return json.loads(content)
11
12class YamlParser(BaseParser):
13    def parse(self, content: str) -> dict:
14        return yaml.safe_load(content)
15
16
17class ParserFactory:
18    """解析器工廠"""
19
20    # 註冊表
21    _parsers: dict[str, type] = {
22        "json": JsonParser,
23        "yaml": YamlParser,
24        "yml": YamlParser,
25    }
26
27    @classmethod
28    def create(cls, parser_type: str) -> BaseParser:
29        """
30        建立解析器
31
32        Args:
33            parser_type: 解析器類型
34
35        Returns:
36            BaseParser: 解析器實例
37
38        Raises:
39            ValueError: 未知的解析器類型
40        """
41        parser_class = cls._parsers.get(parser_type.lower())
42        if parser_class is None:
43            raise ValueError(f"Unknown parser type: {parser_type}")
44        return parser_class()
45
46    @classmethod
47    def register(cls, parser_type: str, parser_class: type) -> None:
48        """註冊新的解析器"""
49        cls._parsers[parser_type.lower()] = parser_class
50
51    @classmethod
52    def get_supported_types(cls) -> list[str]:
53        """取得支援的解析器類型"""
54        return list(cls._parsers.keys())

使用工廠

 1# 建立解析器
 2json_parser = ParserFactory.create("json")
 3yaml_parser = ParserFactory.create("yaml")
 4
 5# 查詢支援的類型
 6types = ParserFactory.get_supported_types()
 7# ['json', 'yaml', 'yml']
 8
 9# 註冊新類型
10class XmlParser(BaseParser):
11    def parse(self, content: str) -> dict:
12        ...
13
14ParserFactory.register("xml", XmlParser)

進階:帶參數的工廠

 1class ParserFactory:
 2    _parsers: dict[str, type] = {}
 3
 4    @classmethod
 5    def create(
 6        cls,
 7        parser_type: str,
 8        **kwargs
 9    ) -> BaseParser:
10        """
11        建立解析器(支援傳入參數)
12
13        Args:
14            parser_type: 解析器類型
15            **kwargs: 傳給解析器的參數
16
17        Example:
18            parser = ParserFactory.create(
19                "json",
20                encoding="utf-8",
21                strict=True
22            )
23        """
24        parser_class = cls._parsers.get(parser_type.lower())
25        if parser_class is None:
26            raise ValueError(f"Unknown parser type: {parser_type}")
27        return parser_class(**kwargs)

使用裝飾器註冊

更優雅的註冊方式:

 1class ParserFactory:
 2    _parsers: dict[str, type] = {}
 3
 4    @classmethod
 5    def register(cls, *names: str):
 6        """
 7        註冊解析器的裝飾器
 8
 9        Example:
10            @ParserFactory.register("json")
11            class JsonParser(BaseParser):
12                ...
13        """
14        def decorator(parser_class: type) -> type:
15            for name in names:
16                cls._parsers[name.lower()] = parser_class
17            return parser_class
18        return decorator
19
20    @classmethod
21    def create(cls, parser_type: str) -> BaseParser:
22        parser_class = cls._parsers.get(parser_type.lower())
23        if parser_class is None:
24            raise ValueError(f"Unknown parser type: {parser_type}")
25        return parser_class()
26
27
28# 使用裝飾器註冊
29@ParserFactory.register("json")
30class JsonParser(BaseParser):
31    def parse(self, content: str) -> dict:
32        return json.loads(content)
33
34
35@ParserFactory.register("yaml", "yml")
36class YamlParser(BaseParser):
37    def parse(self, content: str) -> dict:
38        return yaml.safe_load(content)

根據檔案自動選擇

 1class ParserFactory:
 2    _parsers: dict[str, type] = {}
 3    _extension_map: dict[str, str] = {
 4        ".json": "json",
 5        ".yaml": "yaml",
 6        ".yml": "yaml",
 7        ".xml": "xml",
 8    }
 9
10    @classmethod
11    def create_from_file(cls, file_path: str) -> BaseParser:
12        """
13        根據檔案副檔名自動選擇解析器
14
15        Args:
16            file_path: 檔案路徑
17
18        Example:
19            parser = ParserFactory.create_from_file("config.yaml")
20        """
21        from pathlib import Path
22        ext = Path(file_path).suffix.lower()
23
24        parser_type = cls._extension_map.get(ext)
25        if parser_type is None:
26            raise ValueError(f"Unsupported file extension: {ext}")
27
28        return cls.create(parser_type)

完整範例

 1from abc import ABC, abstractmethod
 2from pathlib import Path
 3
 4
 5class BaseParser(ABC):
 6    """解析器基類"""
 7
 8    @abstractmethod
 9    def parse(self, content: str) -> dict:
10        """解析內容"""
11        pass
12
13    def parse_file(self, path: str) -> dict:
14        """解析檔案"""
15        content = Path(path).read_text(encoding="utf-8")
16        return self.parse(content)
17
18
19class ParserFactory:
20    """解析器工廠"""
21
22    _parsers: dict[str, type] = {}
23    _extensions: dict[str, str] = {}
24
25    @classmethod
26    def register(cls, name: str, extensions: list[str] = None):
27        """
28        註冊解析器的裝飾器
29
30        Args:
31            name: 解析器名稱
32            extensions: 對應的副檔名列表
33        """
34        def decorator(parser_class: type) -> type:
35            cls._parsers[name.lower()] = parser_class
36
37            if extensions:
38                for ext in extensions:
39                    cls._extensions[ext.lower()] = name.lower()
40
41            return parser_class
42        return decorator
43
44    @classmethod
45    def create(cls, parser_type: str) -> BaseParser:
46        """建立解析器"""
47        parser_class = cls._parsers.get(parser_type.lower())
48        if parser_class is None:
49            available = ", ".join(cls._parsers.keys())
50            raise ValueError(
51                f"Unknown parser type: {parser_type}. "
52                f"Available: {available}"
53            )
54        return parser_class()
55
56    @classmethod
57    def create_from_file(cls, file_path: str) -> BaseParser:
58        """根據檔案副檔名自動建立解析器"""
59        ext = Path(file_path).suffix.lower()
60        parser_type = cls._extensions.get(ext)
61
62        if parser_type is None:
63            available = ", ".join(cls._extensions.keys())
64            raise ValueError(
65                f"No parser for extension: {ext}. "
66                f"Supported: {available}"
67            )
68
69        return cls.create(parser_type)
70
71
72# 註冊解析器
73@ParserFactory.register("json", extensions=[".json"])
74class JsonParser(BaseParser):
75    def parse(self, content: str) -> dict:
76        import json
77        return json.loads(content)
78
79
80@ParserFactory.register("yaml", extensions=[".yaml", ".yml"])
81class YamlParser(BaseParser):
82    def parse(self, content: str) -> dict:
83        import yaml
84        return yaml.safe_load(content) or {}
85
86
87# 使用
88def load_config(path: str) -> dict:
89    """載入配置檔案(自動選擇解析器)"""
90    parser = ParserFactory.create_from_file(path)
91    return parser.parse_file(path)
92
93# 使用範例
94config = load_config("settings.yaml")
95config = load_config("data.json")

工廠與依賴注入

工廠模式也可以用於依賴注入:

 1class ServiceFactory:
 2    """服務工廠"""
 3
 4    _services: dict[str, object] = {}
 5
 6    @classmethod
 7    def register_singleton(cls, name: str, instance: object):
 8        """註冊單例服務"""
 9        cls._services[name] = instance
10
11    @classmethod
12    def get(cls, name: str) -> object:
13        """取得服務"""
14        if name not in cls._services:
15            raise ValueError(f"Service not registered: {name}")
16        return cls._services[name]
17
18
19# 應用程式啟動時註冊服務
20ServiceFactory.register_singleton("database", Database())
21ServiceFactory.register_singleton("cache", RedisCache())
22
23# 在其他地方使用
24db = ServiceFactory.get("database")

最佳實踐

1. 提供清楚的錯誤訊息

 1@classmethod
 2def create(cls, parser_type: str) -> BaseParser:
 3    parser_class = cls._parsers.get(parser_type.lower())
 4    if parser_class is None:
 5        available = ", ".join(sorted(cls._parsers.keys()))
 6        raise ValueError(
 7            f"Unknown parser type: '{parser_type}'. "
 8            f"Available types: {available}"
 9        )
10    return parser_class()

2. 支援查詢可用類型

1@classmethod
2def get_available_types(cls) -> list[str]:
3    """返回所有可用的解析器類型"""
4    return sorted(cls._parsers.keys())

3. 考慮快取實例

 1class ParserFactory:
 2    _parsers: dict[str, type] = {}
 3    _instances: dict[str, BaseParser] = {}
 4
 5    @classmethod
 6    def create(cls, parser_type: str, cached: bool = True):
 7        """建立或取得快取的解析器"""
 8        if cached and parser_type in cls._instances:
 9            return cls._instances[parser_type]
10
11        instance = cls._parsers[parser_type]()
12
13        if cached:
14            cls._instances[parser_type] = instance
15
16        return instance

思考題

  1. 工廠模式和直接使用 if-elif 有什麼區別?
  2. 使用裝飾器註冊有什麼優點?
  3. 什麼時候應該快取工廠建立的實例?

實作練習

  1. 實作一個驗證器工廠,支援不同類型的驗證器
  2. 為現有的工廠添加快取功能
  3. 實作一個支援依賴注入的服務工廠

上一章:抽象基類 ABC 下一章:單例與快取模式