百科问答小站 logo
百科问答小站 font logo



如何用最简单的方式解释依赖注入?依赖注入是如何实现解耦的? 第1页

  

user avatar   kongyifei 网友的相关建议: 
      

看了几个高赞答案,感觉说得还是太啰嗦了。依赖注入听起来好像很复杂,但是实际上炒鸡简单,一句话说就是:

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

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

不举抽象的什么造汽车或者小明玩儿手机的例子了。一个很实际的例子,比如我们要用 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(**kw)     try:         yield db     finally:         db.close()  @app.get("/api/users") def list_users(db=Depends(get_db)):     # use the db     ...     

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

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

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


user avatar   gou-qing-chuan 网友的相关建议: 
      

依赖注入(DI)和控制反转(IOC)基本是一个意思,因为说起来谁都离不开谁。

简单来说,a依赖b,但a不控制b的创建和销毁,仅使用b,那么b的控制权交给a之外处理,这叫控制反转(IOC),而a要依赖b,必然要使用b的instance,那么

  1. 通过a的接口,把b传入;
  2. 通过a的构造,把b传入;
  3. 通过设置a的属性,把b传入;

这个过程叫依赖注入(DI)。


那么什么是IOC Container?

随着DI的频繁使用,要实现IOC,会有很多重复代码,甚至随着技术的发展,有更多新的实现方法和方案,那么有人就把这些实现IOC的代码打包成组件或框架,来避免人们重复造轮子。

所以实现IOC的组件或者框架,我们可以叫它IOC Container。




  

相关话题

  为什么当年 Android 选择用 Java 作为开发语言? 
  Java内存模型和Java内存区域的区别和联系? 
  Java 中一个对象a持有对象b中的静态常量,对对象b的回收有什么影响? 
  为什么java版本更新这么慢? 
  有人说 Java 程序员离开框架就什么都不是,如何看待这一言论? 
  谁能用通俗的语言解释一下什么是 RPC 框架? 
  Java为什么设计成`String`不能用`==`比较值? 
  学习java开发,应该重视哪些部分? 
  为什么 Java 总被黑? 
  java转C#的学习路线? 

前一个讨论
如何评价谷歌推出1.6万亿参数超级语言模型Switch Transformer?
下一个讨论
为什么有很多人说 Go 语言不需要依赖注入?





© 2024-11-24 - tinynew.org. All Rights Reserved.
© 2024-11-24 - tinynew.org. 保留所有权利