
在第 20 章我們簡單介紹過 Singleton 與 Factory。今天我們基於 SOLID 原則 (特別是 OCP 開放封閉原則),來看看三個在 Python 專案中非常實用的設計模式。
1. Strategy Pattern (策略模式)
場景:你有一個功能 (例如計算折扣),但演算法有很多種 (滿千送百、會員打折、春節特惠),而且未來可能會一直增加。
解法:把演算法封裝成獨立的「策略」物件,讓 Context 動態切換。
from typing import Protocol
# 定義策略介面 (使用 Protocol 是 Pythonic 的做法)
class DiscountStrategy(Protocol):
def calculate(self, price: float) -> float: ...
# 具體策略 A
class RegularDiscount:
def calculate(self, price: float) -> float:
return price * 0.9 # 九折
# 具體策略 B
class PremiumDiscount:
def calculate(self, price: float) -> float:
return price * 0.8 # 八折
# Context
class ShoppingCart:
def __init__(self, strategy: DiscountStrategy):
self.strategy = strategy
def checkout(self, price):
final_price = self.strategy.calculate(price)
print(f"結帳金額: {final_price}")
# 使用
cart = ShoppingCart(RegularDiscount())
cart.checkout(1000) # 900
這完全符合 OCP,新增折扣規則不用改 ShoppingCart。
2. Adapter Pattern (轉接器模式)
場景:你想用一個外部套件 (例如舊版支付系統),但它的介面跟你現在的系統不相容。你不想為了它改寫你的系統,也不可能去改外部套件的原始碼。
解法:做一個「轉接頭」。
# 你的系統期待的介面
class PaymentGateway:
def pay(self, amount): pass
# 舊系統的介面 (不相容)
class OldBankSystem:
def process_transaction(self, dollars, cents):
print(f"OldBank: {dollars}.{cents}")
# 轉接器
class BankAdapter(PaymentGateway):
def __init__(self, old_system: OldBankSystem):
self.old_system = old_system
def pay(self, amount):
# 轉換邏輯:把 amount 拆成 dollar 和 cents
d = int(amount)
c = int((amount - d) * 100)
self.old_system.process_transaction(d, c)
# 你的程式碼依然只呼叫 pay(),完全不知道後面是舊系統
adapter = BankAdapter(OldBankSystem())
adapter.pay(100.50)
3. Observer Pattern (觀察者模式)
場景:當一個物件狀態改變時 (例如:Youtuber 發布新影片),需要通知所有依賴它的物件 (訂閱者收到通知)。
解法:發布者 (Subject) 維護一份訂閱者 (Observer) 清單。
class YouTubeChannel:
def __init__(self, name):
self.name = name
self.subscribers = [] # 觀察者清單
def subscribe(self, user):
self.subscribers.append(user)
def upload_video(self, title):
print(f"[{self.name}] 上傳了新片:{title}")
self.notify_subscribers(title)
def notify_subscribers(self, title):
for sub in self.subscribers:
sub.update(self.name, title)
class User:
def __init__(self, name):
self.name = name
def update(self, channel, title):
print(f" >> {self.name} 收到通知:{channel} 上片了「{title}」")
# 測試
pewdiepie = YouTubeChannel("PewDiePie")
alice = User("Alice")
bob = User("Bob")
pewdiepie.subscribe(alice)
pewdiepie.subscribe(bob)
pewdiepie.upload_video("Minecraft part 1")
4. 總結
設計模式不是死背硬套的公式,而是前人解決特定問題的「套路」。
- Strategy: 替換演算法 (消除 if-else)。
- Adapter: 介面不相容時的膠水。
- Observer: 一對多的事件通知。
下一章,我們將學習如何保證這些精心設計的架構是正確無誤的——自動化測試與 pytest!
延伸閱讀:
- Refactoring.Guru: Design Patterns (圖文並茂的教學)