Python 進階主題與實務應用 — 設計模式(Singleton、Factory、Observer 等)
簡介
在軟體開發的過程中,設計模式是一套被廣泛驗證的解題方案。它們不只是抽象的概念,而是能直接提升程式碼可讀性、可維護性與彈性的實用工具。對於 Python 開發者而言,掌握常見的設計模式(如 Singleton、Factory、Observer)可以讓你在面對大型專案、多人協作或是需要擴充功能的情境時,快速寫出結構清晰、耦合度低的程式。
本篇文章將以 繁體中文(台灣) 為讀者說明這些模式的核心概念、實作方式與實務應用,並提供完整的程式碼範例、常見陷阱與最佳實踐,幫助 初學者到中級開發者 能夠在自己的 Python 專案中靈活運用。
核心概念
1. Singleton(單例模式)
Singleton 確保一個類別在整個程式執行期間只能產生 唯一 的實例。常見的使用情境包括資料庫連線、設定檔管理、日誌 logger 等,需要全域唯一資源的情況。
實作方式
Python 提供多種實作 Singleton 的技巧,以下示範三種常見寫法:
# 方法一:使用 __new__ 攔截實例化
class SingletonMeta(type):
_instance = None
def __call__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__call__(*args, **kwargs)
return cls._instance
class Logger(metaclass=SingletonMeta):
def log(self, msg):
print(f"[LOG] {msg}")
# 測試
a = Logger()
b = Logger()
assert a is b # 兩者是同一個物件
# 方法二:裝飾器(Decorator)
def singleton(cls):
_instances = {}
def wrapper(*args, **kwargs):
if cls not in _instances:
_instances[cls] = cls(*args, **kwargs)
return _instances[cls]
return wrapper
@singleton
class Config:
def __init__(self):
self.settings = {}
# 測試
c1 = Config()
c2 = Config()
assert c1 is c2
# 方法三:模組層級的單例(最簡單)
# config.py
class Settings:
def __init__(self):
self.debug = True
settings = Settings() # 直接在模組層級建立唯一實例
# 其他模組使用
# from config import settings
# print(settings.debug)
小技巧:若僅需要「全域唯一」的資料結構,直接利用 模組層級變數(方法三)往往是最簡潔且效能最好的做法。
2. Factory(工廠模式)
Factory 模式負責封裝物件的建立過程,讓呼叫端不必直接使用 ClassName(),而是透過工廠方法或工廠類別取得所需的實例。這對於需要根據不同條件產生不同子類別的情況特別有用,例如解析不同檔案格式、建立不同類型的支付方式等。
簡易工廠(Simple Factory)
class Shape:
def draw(self):
raise NotImplementedError
class Circle(Shape):
def draw(self):
print("Draw a circle")
class Square(Shape):
def draw(self):
print("Draw a square")
class ShapeFactory:
@staticmethod
def create(shape_type: str) -> Shape:
if shape_type == "circle":
return Circle()
elif shape_type == "square":
return Square()
else:
raise ValueError(f"Unknown shape: {shape_type}")
# 使用
shape = ShapeFactory.create("circle")
shape.draw() # Output: Draw a circle
工廠方法(Factory Method)
適合當子類別需要自行決定實例化哪個具體產品時使用。
from abc import ABC, abstractmethod
class Document(ABC):
@abstractmethod
def render(self):
pass
class PDFDocument(Document):
def render(self):
print("Render PDF")
class WordDocument(Document):
def render(self):
print("Render Word")
class Creator(ABC):
@abstractmethod
def factory_method(self) -> Document:
pass
def some_operation(self):
doc = self.factory_method()
doc.render()
class PDFCreator(Creator):
def factory_method(self) -> Document:
return PDFDocument()
class WordCreator(Creator):
def factory_method(self) -> Document:
return WordDocument()
# 使用
creator = PDFCreator()
creator.some_operation() # Render PDF
抽象工廠(Abstract Factory)
當系統需要同時產生多個相關產品族(例如 UI 套件的 Windows 與 macOS 版)時,抽象工廠提供一組介面一次產出全部相關物件。
class Button(ABC):
@abstractmethod
def click(self):
pass
class WindowsButton(Button):
def click(self):
print("Windows button clicked")
class MacButton(Button):
def click(self):
print("Mac button clicked")
class GUIFactory(ABC):
@abstractmethod
def create_button(self) -> Button:
pass
class WindowsFactory(GUIFactory):
def create_button(self) -> Button:
return WindowsButton()
class MacFactory(GUIFactory):
def create_button(self) -> Button:
return MacButton()
# 客戶端程式碼
def client_code(factory: GUIFactory):
button = factory.create_button()
button.click()
client_code(WindowsFactory()) # Windows button clicked
client_code(MacFactory()) # Mac button clicked
3. Observer(觀察者模式)
Observer 讓 物件(Subject) 能在狀態改變時,主動通知 一或多個觀察者(Observer)。這是實作 事件驅動、資料變更同步、UI 更新 的常用模式。
基本實作
from typing import List, Protocol
class Observer(Protocol):
def update(self, message: str) -> None:
...
class Subject:
def __init__(self):
self._observers: List[Observer] = []
def attach(self, observer: Observer):
self._observers.append(observer)
def detach(self, observer: Observer):
self._observers.remove(observer)
def notify(self, message: str):
for obs in self._observers:
obs.update(message)
# 具體觀察者
class EmailNotifier:
def update(self, message: str):
print(f"[Email] {message}")
class SmsNotifier:
def update(self, message: str):
print(f"[SMS] {message}")
# 使用
news = Subject()
news.attach(EmailNotifier())
news.attach(SmsNotifier())
news.notify("新產品上線!")
# Output:
# [Email] 新產品上線!
# [SMS] 新產品上線!
進階:使用 weakref 防止記憶體洩漏
import weakref
class WeakSubject:
def __init__(self):
self._observers = weakref.WeakSet()
def attach(self, observer):
self._observers.add(observer)
def notify(self, msg):
for obs in self._observers:
obs.update(msg)
# 觀察者同上,使用方式不變
提示:在大型系統中,若觀察者的生命週期較短,使用
weakref.WeakSet可以避免因循環參考導致的記憶體泄漏。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
| Singleton 產生全域狀態 | 若單例內部保存可變資料,跨模組使用時易產生不可預期的副作用。 | 只在單例中存放 不可變 或 只讀 設定;若需要可變狀態,考慮使用 thread‑safe queue 或 context manager。 |
| Factory 變成「巨型」類別 | 把所有產品的建立邏輯塞在同一個工廠,會違反 單一職責原則。 | 為不同產品族建立 子工廠,或使用 註冊表(registry) 動態映射類別。 |
| Observer 造成記憶體洩漏 | 主體持有觀察者的強參考,觀察者不再需要時仍被保留。 | 使用 weakref.WeakSet 或在 detach 時明確移除。 |
| 過度使用設計模式 | 把每個小功能都套上模式,會讓程式碼變得冗長且難以維護。 | 在 需求確實需要 時才引入模式;先寫簡單、可讀的程式,再視情況重構。 |
| 多執行緒下的 Singleton 非執行緒安全 | 同時建立實例時可能產生多個物件。 | 使用 threading.Lock 包裝 __call__,或直接使用 module‑level singletons(模組本身在 import 時即是單例)。 |
最佳實踐
- 使用型別註解 (
typing) 讓工廠與觀察者的介面更明確。 - 單元測試:對工廠方法、單例取得與觀察者通知寫測試,確保行為不因重構而改變。
- 文件化:在每個模式的類別或函式上加上 docstring,說明使用情境與限制。
- 遵循 PEP 8:保持程式碼風格一致,提升團隊協作效率。
實際應用場景
| 場景 | 適用模式 | 為什麼適合 |
|---|---|---|
| Web 應用的資料庫連線池 | Singleton | 只需要一套連線池,避免重複建立資源。 |
| 多種檔案解析器(JSON、XML、CSV) | Factory / Abstract Factory | 根據檔案副檔名動態產生對應的解析器,保持 client 端程式碼乾淨。 |
| 即時聊天室的訊息推播 | Observer | 使用者訂閱頻道,當有新訊息時即時通知所有觀察者(WebSocket、Email、Push)。 |
| 插件系統(如 IDE、CMS) | Factory + Registry | 透過註冊表讓第三方插件自行註冊類別,核心程式只負責呼叫工廠產生實例。 |
| 跨模組的設定管理 | Singleton (module‑level) | 設定檔只需讀一次,所有模組共用同一個設定物件。 |
案例:簡易插件系統
# plugin_registry.py
_registry = {}
def register(name):
def decorator(cls):
_registry[name] = cls
return cls
return decorator
def create(name, *args, **kwargs):
if name not in _registry:
raise ValueError(f"Plugin {name} not found")
return _registry[name](*args, **kwargs)
# plugins.py
from plugin_registry import register
@register("hello")
class HelloPlugin:
def run(self):
print("Hello from plugin!")
@register("goodbye")
class GoodbyePlugin:
def run(self):
print("Goodbye from plugin!")
# main.py
from plugin_registry import create
p1 = create("hello")
p1.run() # Hello from plugin!
p2 = create("goodbye")
p2.run() # Goodbye from plugin!
此範例使用 Factory + Registry,讓新插件只需要在自己的檔案中 @register 即可自動被系統發現,極大提升擴充性。
總結
設計模式不是一成不變的規則,而是 解決特定問題的工具箱。在 Python 中,Singleton、Factory、Observer 等模式都有其自然的語法表達與最佳實踐:
- Singleton 透過
metaclass、裝飾器或模組層級變數即可安全實作,適合全域唯一資源。 - Factory 系列(Simple、Method、Abstract)讓物件建立與使用解耦,提升程式的彈性與可測試性。
- Observer 為事件驅動與資料同步提供乾淨的通訊機制,配合
weakref可避免記憶體洩漏。
在實務開發時,先從需求出發,判斷是否真的需要引入模式;若需要,則依照上面的範例與最佳實踐來實作,並配合單元測試與完整文件化,才能在專案中真正發揮設計模式的威力。祝你在 Python 的進階旅程中,寫出更乾淨、可維護且易擴充的程式碼!