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
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思考題
- 工廠模式和直接使用
if-elif有什麼區別? - 使用裝飾器註冊有什麼優點?
- 什麼時候應該快取工廠建立的實例?
實作練習
- 實作一個驗證器工廠,支援不同類型的驗證器
- 為現有的工廠添加快取功能
- 實作一個支援依賴注入的服務工廠