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

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 如何用最简单的方式解释依赖注入?

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

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

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

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

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)

依赖翻转之后是这样的:

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 两个参数。

/api/users?offset=100&limit=10
/api/posts?offset=100&limit=10
/api/comments?offset=100&limit=10

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

@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,那么所有函数的参数都需要改,难免会有漏掉的。这时候就可以请出我们的老朋友依赖注入了。

# 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,只需要更改依赖就好了,所有依赖它的函数都无需做任何改动。

def get_page_info(page: int, size: int):
    # page 从 1 开始,offset 从 0 开始
    return {"offset": page * limit - limit: ,"limit": size}

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

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 几乎一样了,不再赘述。

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

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

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
7月前
|
存储 C++
【C++】多态(重写)的实现过程及其原理【核心知识点精讲】(22)
【C++】多态(重写)的实现过程及其原理【核心知识点精讲】(22)
|
2月前
|
搜索推荐
用通俗易懂的方式解释一下多态
【10月更文挑战第13天】多态,就像是编程世界里的一场神奇魔术,它让不同的对象在面对相同的操作时,能够展现出各自独特的表现。
24 2
|
6月前
|
JSON IDE Java
Java反射详解:核心概念、使用方法与实际应用
Java反射详解:核心概念、使用方法与实际应用
87 2
|
6月前
|
XML Java 数据格式
Spring5系列学习文章分享---第三篇(AOP概念+原理+动态代理+术语+Aspect+操作案例(注解与配置方式))
Spring5系列学习文章分享---第三篇(AOP概念+原理+动态代理+术语+Aspect+操作案例(注解与配置方式))
57 0
|
7月前
|
缓存 监控 Java
Hysterix的概念、作用、使用方法
Hysterix的概念、作用、使用方法
69 0
|
NoSQL Redis
如何用最简单的方式解释依赖注入?依赖注入是如何实现解耦的?
如何用最简单的方式解释依赖注入?依赖注入是如何实现解耦的?
72 0
|
Java 容器 Spring
扩展原理 :Spring注解笔记系列(二)
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {} -- 如果想要写一个监听器就要实现这个接口,ApplicationEvent就是代表要监听的事件
118 0
|
设计模式 Java 数据库连接
对象构造神器,建造者模式实操分享
建造者模式,简单的说,就是将对象的属性与创建分离,使得同样的构建过程可以创建不同的对象。 初次接触这个概念,可能有点闷逼,下面我们来举例,你就好懂了! 假设有一个对象里面有20个属性,如果我要使用这个对象,我们可能需要把这20个属性弄明白,然后在构造函数或者创建一个对象通过set一个一个去指定,显然这对开发者来说非常吃力!
对象构造神器,建造者模式实操分享
|
前端开发 Java 数据库连接
SSM框架原理,作用及使用方法
SSM框架原理,作用及使用方法
230 0
|
XML Java 数据库连接
Spring框架学习 (二) 依赖注入的两类形式(下)
Spring框架学习 (二) 依赖注入的两类形式(下)
137 0
下一篇
DataWorks