你们好,这是我在公众号上发的第一篇编程文章,但愿你们喜欢前端
前几天在给一位同窗上课,中途咱们讲到了一个叫作依赖注入(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 的代码也得跟着改。这就是耦合程度太高。牵一发而动全身是最糟糕的软件工程。
如何解决?
由于 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 的视频)
在讲解以前,咱们直接来看修改后的代码吧。
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、前端。
想学编程的读者请关注个人公众号(发送消息“私教”咨询私人教学)