Python 軟體架構基石 (二):實用設計模式 (第 32 章)

站主自己的課程,請大家支持
揭秘站長的架站心法:如何利用 Hugo × AI 打造高質感個人品牌網站? 揭秘站長的架站心法:如何利用 Hugo × AI 打造高質感個人品牌網站?
  • Post by
  • Jan 15, 2024
post-thumb

在第 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


延伸閱讀

LATEST POST
TAG