Python 軟體架構基石 (一):SOLID 原則 (第 31 章)

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

隨著專案越來越大,如果缺乏良好的架構設計,程式碼會變得像義大利麵一樣糾纏不清(Spaghetti Code),這時「牽一髮而動全身」,改一個 bug 卻產生三個新 bug。

SOLID 原則是五個設計原則的縮寫,由 Robert C. Martin (Uncle Bob) 整理提出,旨在讓軟體更易維護、更易擴充。

1. S - 單一職責原則 (SRP)

Single Responsibility Principle

一個類別應該只有一個改變的理由。(做好一件事)

❌ 錯誤範例UserManager 既管登入,又管寄 email,還管寫 log。

class UserManager:
    def login(self, username, password):
        # 驗證邏輯...
        pass
        
    def send_email(self, message):
        # SMTP 邏輯...
        pass

✅ 正確範例:拆分職責。

class EmailService:
    def send(self, message): ...

class Logger:
    def log(self, info): ...

class UserManager:
    def __init__(self, email_service: EmailService, logger: Logger):
        self.email = email_service
        self.log = logger
        
    def login(self, username, password):
        # 只專注於登入邏輯
        self.log.log(f"{username} logged in")
        self.email.send("Welcome!")

2. O - 開放封閉原則 (OCP)

Open-Closed Principle

軟體實體應該對「擴充」開放,對「修改」封閉。

要新增功能時,應該是加新代碼,而不是改舊代碼

❌ 錯誤範例:每次新增付款方式都要改 PaymentProcessorif-else

class PaymentProcessor:
    def pay(self, type, amount):
        if type == "credit":
            # 信用卡邏輯
        elif type == "paypal":
            # PayPal 邏輯

✅ 正確範例:使用多型 (Polymorphism)。

from abc import ABC, abstractmethod

class PaymentMethod(ABC):
    @abstractmethod
    def pay(self, amount): pass

class CreditCard(PaymentMethod):
    def pay(self, amount): print(f"刷卡 {amount}")

class PayPal(PaymentMethod):
    def pay(self, amount): print(f"PayPal {amount}")

# 新增 LinePay 只需要新增一個 class,完全不用動舊程式碼
class LinePay(PaymentMethod):
    def pay(self, amount): print(f"LinePay {amount}")

3. L - 里氏替換原則 (LSP)

Liskov Substitution Principle

子類別必須能完全替換掉父類別,而不會讓程式出錯。

如果你繼承了一個類別,卻在方法裡丟出 NotImplementedError 或者改變了行為預期,那就違反了 LSP。

❌ 錯誤範例:鴕鳥是鳥,但如果 Birdfly() 方法, Ostrich 繼承它卻不能飛,就會出事。

4. I - 介面隔離原則 (ISP)

Interface Segregation Principle

客戶端不應該強迫依賴它用不到的介面。

Python 用 ABC (Abstract Base Class) 來定義介面。不要定義一個「胖介面」塞滿所有功能,而是定義多個「小介面」。

❌ 錯誤範例MultiFunctionDevice 介面強迫可以列印的機器也要實作傳真功能。

5. D - 依賴反轉原則 (DIP)

Dependency Inversion Principle

高層模組不應依賴低層模組,兩者都應依賴抽象。

這通常透過 依賴注入 (Dependency Injection) 來實現。在 SRP 的範例中,UserManager 依賴的是 EmailService 類別 (或介面),而不是在內部直接 import smtplib 寫死發信邏輯。

6. 總結

SOLID 是寫出高品質物件導向程式碼的基石:

  1. SRP: 各司其職。
  2. OCP: 擴充不修舊。
  3. LSP: 繼承不只為了重用,要是真正的 Is-A 關係。
  4. ISP: 介面要小而美。
  5. DIP: 依賴抽象不依賴實作。

下一章,我們將基於這些原則,繼續探討更多實用的 Design Patterns (設計模式) PART II


延伸閱讀

LATEST POST
TAG