Python 中的依賴注入:必要的複雜性還是多餘的設計?

前言

依賴注入(Dependency Injection)是軟體開發中一種重要的設計模式,它的核心思想非常簡單:一個類不應該負責創建自己所需的依賴對象,而應該從外部接收它們。這種模式有效地實現了「控制反轉」(IoC) 原則,將對象創建的責任從類本身轉移到外部框架或容器。

什麼是依賴注入?

依賴注入是控制反轉(IoC)的一種實現形式,它將對象的創建和管理責任從類內部轉移到外部。這種簡單的模式帶來了幾個關鍵好處

  • 解耦
  • 可測試性提高
  • 配置集中化
  • 更好的關注點分離

舉個例子,一般來說,我們會直在在 class 直接實例化數據庫連接:

class UserService:
    def __init__(self):
        self.db = Database()  # 直接創建依賴
    
    def get_user(self, user_id):
        return self.db.find_user(user_id)

而使用依賴注入後,程式碼會變成:

class UserService:
    def __init__(self, db):  # 依賴從外部注入
        self.db = db
    
    def get_user(self, user_id):
        return self.db.find_user(user_id)

這個設計實現了控制反轉原則,假如團隊之後想換資料庫,這樣的寫法不僅更靈活(可以替換成不同的資料庫),在寫單元測試時也更容易測試(可以注入測試用的資料庫),以及模組間的依賴關係也一目了然。

儘管依賴注入在 Java、C# 等語言中已經成為標準實踐,但在 Python 專案中卻相對罕見。

👉 延伸閱讀:淺談單元測試的重要性

為什麼依賴注入在 Python 專案中較不常見?

  • 專案規模通常較小:相較於 Java、C# 而言,Python 的專案規模通常較小,不需要太複雜的依賴關係,且這樣的專案會比較要求執行效率,導入 DI 增加的額外開銷也是一個考量之一。
  • 模組系統的便利性:​Python 的模組能夠直接匯入並使用模組中的物件,這種方式就已經提供了一種簡單的依賴管理機制。
  • 動態程式語言特性:動態程式語言和弱型別的特性可以讓我們簡單替換物件。
  • 許多替代方案:Python 提供了其他方式來實現類似 DI 的效果,例如使用函數參數、裝飾器或上下文管理器等,這些方式更符合 Python 的風格,也更有可讀性。

Python 的 import 可以動態決定導入哪個模組:

# 動態決定導入哪個模組
if config.use_postgres:
    from .postgres_db import Database
else:
    from .sqlite_db import Database

在 Java 需要依賴注入框架處理的事,Python 可能只需一行程式碼:

def process_data(database=get_default_database()):
  # 可以直接使用 database 或預設值

總結

最後,是否採用依賴注入還是要考慮專案的具體需求、團隊的技術背景以及長期維護的考量,依賴注入在 Python 專案中雖然少見,但在複雜的商業邏輯中仍然是一個非常好的設計,當依賴關係變得越來越複雜時,才是依賴注入發揮的開始。