設計範式之依賴注入:一個 Python Flask 的例子

你們好,這是我在公衆號上發的第一篇編程文章,但願你們喜歡前端

前幾天在給一位同窗上課,中途咱們講到了一個叫作依賴注入(Dependency Injection)的設計範式。而後我佈置做業,說將後端代碼從「直接使用全局變量」重構成「依賴注入」。git

次日這位同窗說並無搞懂怎麼弄。因此我以爲有必要在這裏給出具體例子,讓你們能夠一目瞭然。github


首先咱們來看具體的問題。數據庫

咱們有一個簡單的 Python Flask 應用,大體長這個樣子。編程

from flask import Flask

app = Flask("example")

class DAO:
    def __init__(self):
        self.data = []

dao = DAO()

@app.route("/")
def m():
    return dao.data

if __name__ == "__main__":
    app.run()
複製代碼

DAO 是 Data Access Object, 在一個真正的後端應用中,多半是一個數據庫的鏈接點。它的職責就是提供不一樣數據的CRUD(Create,Read,Update,Delete)。固然,這位同窗課上的應用比這個例子有更多的 REST 路由和 DAO。這裏爲了簡便,將代碼大量簡化了。flask

一切都運做良好,惟一有個小問題:dao 是個全局變量。後端

爲何在這裏使用全局變量會是一個問題呢?咱們能夠從測試與功能拓展的容易程度來看。app

首先,從測試的角度,使用全局變量讓單元測試(Unit Test)的繁瑣程度與難度直線上升。函數

一般狀況下,我僅僅是想測試 REST API handler ,並不想測試 dao 自己的邏輯。使用全局變量讓這幾乎辦不到了。由於每一個 handler 直接使用了 dao 這個變量。也就是說,每次運行一個 handler,都會運行 dao 自己的邏輯。若是 dao 是一個真正的數據庫鏈接,那麼每一個測試都會與數據庫進行交互。這就變成了整合測試(Integration Test)。有些時候,咱們的確須要這樣作。但這不是單元測試的目的。單元測試

其次,從功能拓展的角度,若是 dao 的接口被改動了,那麼由於每一個 handler 都直接使用那個全局變量,因此頗有可能形成多個 handler 的代碼也得跟着改。這就是耦合程度太高。牽一髮而動全身是最糟糕的軟件工程。

如何解決?

1. Monkey Patch (糟糕的方法)

由於 Python 是一個超級反射、超級元編程的語言,因此不少人會使用一種叫作 monkey patch 的方法。簡單地說,就是在一個做用域(scope)中,將目標對象的某些成員替換掉,而後在離開這個 scope 時,將以前被替換掉的成員替換回來。

由於 Python 的文件就是模組,而一個模組也是一個對象,因此能夠在每一個單元測試中將 dao 這個全局變量給替換成測試所用的實現。

這是一個糟糕的方法。首先 monkey patch 老是要寫不少沒必要要的代碼。並且 monkey patch 要求測試的做者必須 100% 記住被測試對象的源代碼依賴關係,否則幾乎就要寫錯。Type 信息也更有可能丟失。由於 monkey patch 發生在 runtime,而不是 load time,因此 IDE 幾乎也給不出不少提示。

無論從 OO Design 的角度仍是實踐的角度,monkey patch 都是一種劣等的策略。我只在沒有辦法的時候,才使用 monkey patch。(好比代碼庫已經太耦合)

(沒有看懂 monkey patch 的同窗不用擔憂,由於它根本不中用。若是你好奇,我也許能夠單獨作一個 monkey patch 的視頻)

2. Dependency Injection 依賴注入 (優等的方法)

在講解以前,咱們直接來看修改後的代碼吧。

from flask import Flask

class DAO:
    def __init__(self):
        self.data = []

def App(dao):
    app = Flask("example")

 @app.route("/")
    def m():
        return dao.data

    return app

if __name__ == "__main__":
    app = App(DAO())
    app.run()
複製代碼

這裏咱們作的改動其實很簡單。定義了一個 App 函數,其做用是初始化咱們的 Flask app。dao 做爲一個參數傳進來。這讓整個代碼的耦合性直線降低了。那麼在測試中,每一個單元測試能夠單獨調用一次 App 函數,獲得一個單獨的 Flask 實例,而後 App(dao) 的參數 dao 能夠是單獨實現的。

這樣第一不用管 Flask 的 handler 和 DAO Class 的源代碼依賴,由於他們之間沒有依賴了。handler 如今只依賴於一個有 data 成員的對象,而不是 DAO Class 的實例。

第二,由於測試沒有依賴於同一個全局變量,因此每一個測試也是相互獨立的。不用擔憂本身對數據的操做會影響其餘測試。

結語

Dependency Injection 還有不少能夠講的。這裏只是給出一個簡單的 Flask 例子而已。本文並無對 Flask 或者 Python 的細節作出具體解釋。若是你有任何疑惑,歡迎留言!


也歡迎你們關注個人 B 站:space.bilibili.com/16696495

代碼在 gist.github.com/CreatCodeBu…

關於我

我是一名在硅谷打工的碼農,平時就寫寫代碼,教教課,才能維持生活這樣子。

我教課的內容包括 Go、Python、GraphQL、JavaScript、前端。

想學編程的讀者請關注個人公衆號(發送消息「私教」諮詢私人教學)

相關文章
相關標籤/搜索