前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何用最简单的方式解释依赖注入?

如何用最简单的方式解释依赖注入?

作者头像
爬虫技术学习
发布2023-03-06 14:40:28
3600
发布2023-03-06 14:40:28
举报
文章被收录于专栏:爬虫技术学习爬虫技术学习

依赖注入听起来好像很复杂,但是实际上超级简单,一句话说就是:

本来我接受各种参数来构造一个对象,现在只接受一个参数——已经实例化的对象。

也就是说我对对象的『依赖是注入进来的』,而和它的构造方式解耦了。构造和销毁这些『控制』操作也交给了第三方,也就是控制『反转』。

不举抽象的例子了。一个很实际的例子,比如我们要用 redis 实现一个远程列表。耦合成一坨的代码可以是这样写,其中我们需要自己构造需要用的组件:

代码语言:javascript
复制
class RedisList:
    def __init__(self, host, port, password):
        self._client = redis.Redis(host, port, password)

    def push(self, key, val):
        self._client.lpush(key, val)

l = RedisList(host, port, password)

依赖翻转之后是这样的:

代码语言:javascript
复制
class RedisList:
    def __init__(self, redis_client)
        self._client = redis_client

    def push(self, key, val):
        self._client.lpush(key, val)

redis_client = get_redis_client(...)
l = RedisList(redis_client)

看起来好像也没什么区别,但是考虑下面这些因素:

  1. 线下线上环境可能不一样,get_redis_client 函数在线上可能要做不少操作来读取到对应的配置,可能并不是不是一个简单的函数。在测试环境可能会返回一个 Mock 的 FakeRedis。
  2. redis 这个类是一个基础组件,可能好多类都需要用到,每个类都去自己实例化吗?如果需要修改的话,每个类都要改。
  3. 我们想依赖的是 redis 的 lpush 方法,而不是他的构造函数。

所以把 redis 这个类的实例化由一个单一的函数来做,而其他函数只调用对应的接口是有意义的。

Web 框架中的依赖注入

上面提到的是依赖注入的原始定义,在实际开发过程中,Web 框架领域最喜欢提依赖注入这个 buzz word。由于本人太笨了,一直没学会 Java 和 Spring Framework,这里以 Python 的 FastAPI 为例。我们将会看到,Web 框架领域的依赖注入依然没有脱离它的原始定义。

假设我们有如下三个 API,它们都返回一个列表且支持分页,所以都需要 offset 和 limit 两个参数。

代码语言:javascript
复制
/api/users?offset=100&limit=10
/api/posts?offset=100&limit=10
/api/comments?offset=100&limit=10

我们可以这样实现,其中 handler 函数的参数就是 URL 中的参数:

代码语言:javascript
复制
@app.get("/api/users")
def list_users(offset: int, limit: int):
    return UserModel.filter(offset=offset, limit=limit)

@app.get("/api/posts")
def list_posts(offset: int, limit: int):
    return PostModel.filter(offset=offset, limit=limit)

@app.get("/api/posts")
def list_comments(offset: int, limit: int):
    return CommentModel.filter(offset=offset, limit=limit)

虽然参数不多,但是这里已经可以嗅到一丝代码重复的味道了。不过更重要的是,假如我们要改一下参数呢?比如说从 limit/offset 改成 page/size,那么所有函数的参数都需要改,难免会有漏掉的。这时候就可以请出我们的老朋友依赖注入了。

代码语言:javascript
复制
# fastapi 中提供了 Depends 用来表示依赖
from fastapi import Depends

def get_page_info(offset: int, limit: int):
    return {"offset": limit, "limit": limit}

# list_users 依赖了 get_page_info 函数,而不再负责具体的 offset/limit 参数
@app.get("/api/users")
def list_users(page_info: dict = Depends(get_page_info)):
    return UserModel.filter(**page_info)

# posts, comments 等类似

和开篇的一句话类似:list_users 本来接受具体的参数来获取翻页信息,而现在只接受一个已经实例化过后的 page_info 对象了。也就是说 page_info 这个依赖被框架注入到了具体的业务代码中。

假如我们需要把参数变成 page/size,只需要更改依赖就好了,所有依赖它的函数都无需做任何改动。

代码语言:javascript
复制
def get_page_info(page: int, size: int):
    # page 从 1 开始,offset 从 0 开始
    return {"offset": page * limit - limit: ,"limit": size}

再来一个例子,如果我们每个 handler 函数都依赖一个数据库链接:

代码语言:javascript
复制
def get_db():
    db = connect(...)
    try:
        yield db
    finally:
        db.close()

@app.get("/api/users")
def list_users(db=Depends(get_db)):
    # use the db
    ...

这个例子就和最上面的 get_redis_client 几乎一样了,不再赘述。

总而言之,依赖注入在代码上很简单,就是把一坨参数换成了一个实例参数。

设计模式不是发明出来的,而是总结出来的,可能不经意间你早就在用依赖注入了。没必要一写代码就想着我要用这个那个设计模式,只会缚住自己的手脚,当你发现一个项目里有三处雷同的代码,再用合理的设计模式解决这个问题也不迟。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-05-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 爬虫技术学习 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档