本文 AI 產出,尚未審核

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 queuecontext manager
Factory 變成「巨型」類別 把所有產品的建立邏輯塞在同一個工廠,會違反 單一職責原則 為不同產品族建立 子工廠,或使用 註冊表(registry) 動態映射類別。
Observer 造成記憶體洩漏 主體持有觀察者的強參考,觀察者不再需要時仍被保留。 使用 weakref.WeakSet 或在 detach 時明確移除。
過度使用設計模式 把每個小功能都套上模式,會讓程式碼變得冗長且難以維護。 需求確實需要 時才引入模式;先寫簡單、可讀的程式,再視情況重構。
多執行緒下的 Singleton 非執行緒安全 同時建立實例時可能產生多個物件。 使用 threading.Lock 包裝 __call__,或直接使用 module‑level singletons(模組本身在 import 時即是單例)。

最佳實踐

  1. 使用型別註解 (typing) 讓工廠與觀察者的介面更明確。
  2. 單元測試:對工廠方法、單例取得與觀察者通知寫測試,確保行為不因重構而改變。
  3. 文件化:在每個模式的類別或函式上加上 docstring,說明使用情境與限制。
  4. 遵循 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 的進階旅程中,寫出更乾淨、可維護且易擴充的程式碼!