4.3 工廠模式
4.3 工廠模式
工廠模式用於封裝物件的建立邏輯,讓使用者不需要知道具體的類別名稱,只需要提供識別資訊即可取得適當的物件。
工廠把建立邏輯從呼叫端收走
問題場景
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
16class ParserFactory:
17 """解析器工廠"""
18
19 # 註冊表
20 _parsers: dict[str, type] = {
21 "json": JsonParser,
22 "yaml": YamlParser,
23 "yml": YamlParser,
24 }
25
26 @classmethod
27 def create(cls, parser_type: str) -> BaseParser:
28 """
29 建立解析器
30
31 Args:
32 parser_type: 解析器類型
33
34 Returns:
35 BaseParser: 解析器實例
36
37 Raises:
38 ValueError: 未知的解析器類型
39 """
40 parser_class = cls._parsers.get(parser_type.lower())
41 if parser_class is None:
42 raise ValueError(f"Unknown parser type: {parser_type}")
43 return parser_class()
44
45 @classmethod
46 def register(cls, parser_type: str, parser_class: type) -> None:
47 """註冊新的解析器"""
48 cls._parsers[parser_type.lower()] = parser_class
49
50 @classmethod
51 def get_supported_types(cls) -> list[str]:
52 """取得支援的解析器類型"""
53 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@ParserFactory.register("json")
29class JsonParser(BaseParser):
30 def parse(self, content: str) -> dict:
31 return json.loads(content)
32
33@ParserFactory.register("yaml", "yml")
34class YamlParser(BaseParser):
35 def parse(self, content: str) -> dict:
36 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
4class BaseParser(ABC):
5 """解析器基類"""
6
7 @abstractmethod
8 def parse(self, content: str) -> dict:
9 """解析內容"""
10 pass
11
12 def parse_file(self, path: str) -> dict:
13 """解析檔案"""
14 content = Path(path).read_text(encoding="utf-8")
15 return self.parse(content)
16
17class ParserFactory:
18 """解析器工廠"""
19
20 _parsers: dict[str, type] = {}
21 _extensions: dict[str, str] = {}
22
23 @classmethod
24 def register(cls, name: str, extensions: list[str] = None):
25 """
26 註冊解析器的裝飾器
27
28 Args:
29 name: 解析器名稱
30 extensions: 對應的副檔名列表
31 """
32 def decorator(parser_class: type) -> type:
33 cls._parsers[name.lower()] = parser_class
34
35 if extensions:
36 for ext in extensions:
37 cls._extensions[ext.lower()] = name.lower()
38
39 return parser_class
40 return decorator
41
42 @classmethod
43 def create(cls, parser_type: str) -> BaseParser:
44 """建立解析器"""
45 parser_class = cls._parsers.get(parser_type.lower())
46 if parser_class is None:
47 available = ", ".join(cls._parsers.keys())
48 raise ValueError(
49 f"Unknown parser type: {parser_type}. "
50 f"Available: {available}"
51 )
52 return parser_class()
53
54 @classmethod
55 def create_from_file(cls, file_path: str) -> BaseParser:
56 """根據檔案副檔名自動建立解析器"""
57 ext = Path(file_path).suffix.lower()
58 parser_type = cls._extensions.get(ext)
59
60 if parser_type is None:
61 available = ", ".join(cls._extensions.keys())
62 raise ValueError(
63 f"No parser for extension: {ext}. "
64 f"Supported: {available}"
65 )
66
67 return cls.create(parser_type)
68
69# 註冊解析器
70@ParserFactory.register("json", extensions=[".json"])
71class JsonParser(BaseParser):
72 def parse(self, content: str) -> dict:
73 import json
74 return json.loads(content)
75
76@ParserFactory.register("yaml", extensions=[".yaml", ".yml"])
77class YamlParser(BaseParser):
78 def parse(self, content: str) -> dict:
79 import yaml
80 return yaml.safe_load(content) or {}
81
82# 使用
83def load_config(path: str) -> dict:
84 """載入配置檔案(自動選擇解析器)"""
85 parser = ParserFactory.create_from_file(path)
86 return parser.parse_file(path)
87
88# 使用範例
89config = load_config("settings.yaml")
90config = 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# 應用程式啟動時註冊服務
19ServiceFactory.register_singleton("database", Database())
20ServiceFactory.register_singleton("cache", RedisCache())
21
22# 在其他地方使用
23db = 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思考題
- 工廠模式和直接使用
if-elif有什麼區別? - 使用裝飾器註冊有什麼優點?
- 什麼時候應該快取工廠建立的實例?
實作練習
- 實作一個驗證器工廠,支援不同類型的驗證器
- 為現有的工廠添加快取功能
- 實作一個支援依賴注入的服務工廠