V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
llej
V2EX  ›  程序员

对于依赖注入的思考

  •  
  •   llej · 2 天前 · 2087 次点击

    突然想明白了为什么要有依赖注入了:这里只讨论在组合优于继承的这种情况下 一定会有许多的函数组合情况,而且还会有函数组合的组合,这样实质上形成了一个函数之间的依赖链路。

    如果是直接在代码中硬编码对应的函数组合的话,我要实现一个新的高阶组合他和原来的高阶组合的唯一区别只是一个基础函数的实现不一致,那我基本需要重新复制一遍原来的组合代码,然后修改其中一个调用

    如果要想复用原来的高阶组合,而只是修改其中对于该基础函数的替换的话,要么将该函数作为参数传递(这个传递链路可能很长),而依赖注入就是为了解决这个问题而存在的。

    有了依赖注入,只需要在编写代码的时候就通过依赖注入来调用函数,后续的替换就十分的方便了。

    function baseFn_A(){}
    function baseFn_B(){}
    function higherFn_B(){
    	baseFn_A()
    	//other baseFn ...
    }
    
    function higherFn_C(){
    	higherFn_B()
    	//other baseFn / higherFn ...
    }
    // 这中间还可能有更多层次的这样的套娃
    
    // 上面存在一个依赖链路 higherFn_C>higherFn_B>baseFn_A
    // 如果我要实现一个新的函数 higherFn_C2 ,它和 higherFn_C 唯一的区别就是调用的是 baseFn_B 而非 baseFn_A
    // 我认为依赖注入就是为了更方便的创建 higherFn_C2 而不需要改动特别多的代码
    

    如何实现依赖注入

    依赖注入可以使用链表来理解。js 的原型链就可以认为是一种依赖注入

    只要实现 injectprovide 这两个函数就可以了

    provide 实现 :接受一个 key 和 value , 将这两个参数和当前节点相绑定

    inject 实现 :接受一个 key , 在任意一个节点,沿父级一直向上通过 key 查询对应的注入的值,找到了就返回,没有就找父级的父级。

    inject 的实现基本和 js 对象基于原型链查找属性值的实现方式是一样的。所以我说 <u>js 的原型链就可以认为是一种依赖注入</u>

    如何使用链表来理解呢:

    比如 vue 中的依赖注入系统,这里的链表是组件树中,将组件理解为节点,然后一直查找上一层组件,一直到顶层,其中经过的所有节点就是一个链表,基于这个链表,最末端的节点,可以获取他的任意一个父级节点 provide 的值,并且在 key 相同的情况下 inject 的是离他最近的一个节点 provide 的值

    但如果我们要以函数为节点,以函数调用栈作为链表来实现一个依赖注入系统可行吗?

    答案是可以但又不可以,因为在浏览器中是受限的,只能基于 zone.js 来实现(存在缺陷)

    在 node.js 的环境下可以选择使用 Async hooks 来达成同样的目的

    20 条回复    2025-01-22 16:47:23 +08:00
    sapjax
        1
    sapjax  
       2 天前   ❤️ 1
    首先要区分依赖倒置和依赖注入
    依赖注入是实现依赖倒置的一个环节
    依赖倒置的目的不是为了方便,而是管理依赖的方向,避免环形依赖
    让具体的外部实现依赖一个内部的 Interface ,避免内部依赖具体的外部实现,这样就把依赖的方向倒转过来了
    那么外部实现是如何生效的呢,这就涉及到依赖注入,通过自动注入让内部实现调用 Interface 的时候,可以找到正确的实现
    importmeta
        2
    importmeta  
       2 天前
    icode1688
        3
    icode1688  
       2 天前
    inversify/InversifyJS 竟然不支持 typescript 5.x
    Orangeee
        4
    Orangeee  
       2 天前
    JS 原型链 和 DI 没有关系,相似点可能是都复用了代码
    llej
        5
    llej  
    OP
       2 天前
    @icode1688 但这些都是基于类的,我最想要的是基于函数调用的。
    llej
        6
    llej  
    OP
       2 天前
    @importmeta 但这些都是基于类的,我最想要的是基于函数调用的。
    runlongyao2
        7
    runlongyao2  
       2 天前
    对于类是第一公民的语言,比如 JAVA,C#,依赖注入能降低复杂度。但是对于 JS ,GO 这类语言就大可不必。原因也很简单,JAVA 的表达方式基于类,灵活性是很差的,所以需要所谓的设计模式去弥补
    importmeta
        8
    importmeta  
       2 天前
    @llej 函数用组合模式的多吧, 比如 React 的 Hooks.
    mcfog
        9
    mcfog  
       2 天前
    虽然都是 js ,原型链和组件-父组件构成的链完全不是一回事儿

    用链表理解依赖注入怎么想都是歪的

    OP 后面描述的一些行为,比起依赖注入,似乎更像职责链模式
    newaccount
        10
    newaccount  
       2 天前
    DI 是用来处理 OOP 的,不管 FP 这一摊子
    3085570450tt
        11
    3085570450tt  
       2 天前
    @importmeta 也推荐用 inversifyJS 的一个项目,Theia https://theia-ide.org/
    lixiaolin123
        13
    lixiaolin123  
       2 天前
    @runlongyao2 想知道 go 是如何使用回调的。java 我知道有个 Obcsrver 设计模式来实现回调,go 是否在实现回调上比 java 简单?
    GeekGao
        14
    GeekGao  
       1 天前
    认同 1 楼观点。

    不过补充一下:
    依赖倒置原则:是一种设计思想,它告诉我们应该依赖抽象,而不是具体实现。
    依赖注入:是一种实现方式,它通过外部注入依赖,帮助我们实现依赖倒置原则。


    依赖倒置原则就像是一个“万能遥控器” 它不直接依赖具体的电器,而是依赖一个通用的接口。这样,无论电器如何变化,遥控器都能正常工作,系统也更易于维护和扩展。

    依赖注入过程就像电器独立实现自己的核心控制逻辑。然后通过蓝牙协议与“万能遥控器” 进行连接,这个过程,不会对万能遥控器进行任何代码更改。

    核心思想就是:高层模块(遥控器)不应该依赖低层模块(电器),而是两者都应该依赖抽象(电源开关接口)。
    charlie21
        15
    charlie21  
       1 天前
    @icode1688 inversify 6.2.1 和 typescript 5.7.3 在项目里同时使用是没发现问题的
    AEnjoyable
        16
    AEnjoyable  
       1 天前
    @lixiaolin123 go 的函数参数可以传递函数,然后还支持匿名函数 那想做回调就很好办
    runlongyao2
        17
    runlongyao2  
       1 天前
    @lixiaolin123
    package main

    import "fmt"

    // 定义一个回调函数类型
    type Callback func(int) int

    // 处理函数,接受一个回调函数作为参数
    func process(value int, callback Callback) {
    result := callback(value)
    fmt.Println("Callback result:", result)
    }

    // 一个示例回调函数
    func double(x int) int {
    return x * 2
    }

    // 另一个示例回调函数
    func square(x int) int {
    return x * x
    }

    func main() {
    value := 5

    // 使用 double 作为回调函数
    process(value, double)

    // 使用 square 作为回调函数
    process(value, square)
    }

    GPT 生成的和 JS 很类似
    runlongyao2
        18
    runlongyao2  
       1 天前
    @lixiaolin123 OOP 的年代已经过去了,那会儿 OOP 大行其道,所以 JAVA 和 C#都是基于这套东西设计的,之后设计的语言可能都不存在类这个概念比如 GO
    icode1688
        19
    icode1688  
       1 天前
    @charlie21 喔不好意思,我表达有误,是不支持 stage 3 阶段的注解,也就是 tsconfig.json 中不需要配置 experimentalDecorators: true
    emitDecoratorMetadata: true

    TypeScript 5.0 版本,支持 stage3 阶段的装饰器写法。

    https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#decorators
    johnhom
        20
    johnhom  
       2 小时 7 分钟前
    @llej #6 其实都上了依赖注入,最好就是跟着用类去写,同时继承接口的写法也更易懂,而且这样符合依赖倒置原则(高层模块不应依赖于低层模块,二者应依赖于抽象)方便代码的解耦。
    给你看示例代码感受一下:



    而且我可以通过继承 LocalStorageService 这个类,来增加更多额外的功能。当然也可以控制继承的嵌套,我觉得用函数来写的话有点没有那么容易理解
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3030 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 10:55 · PVG 18:55 · LAX 02:55 · JFK 05:55
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.