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

对于依赖注入的思考

  •  
  •   llej 264 天前 3489 次点击
    这是一个创建于 264 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

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

    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 来达成同样的目的

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

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

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

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


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

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

    核心思想就是:高层模块(遥控器)不应该依赖低层模块(电器),而是两者都应该依赖抽象(电源开关接口)。
    charlie21
        15
    charlie21  
       264 天前
    @icode1688 inversify 6.2.1 和 typescript 5.7.3 在项目里同时使用是没发现问题的
    AEnjoyable
        16
    AEnjoyable  
       263 天前   1
    @lixiaolin123 go 的函数参数可以传递函数,然后还支持匿名函数 那想做回调就很好办
    runlongyao2
        17
    runlongyao2  
       263 天前   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  
       263 天前   1
    @lixiaolin123 OOP 的年代已经过去了,那会儿 OOP 大行其道,所以 JAVA 和 C#都是基于这套东西设计的,之后设计的语言可能都不存在类这个概念比如 GO
    icode1688
        19
    icode1688  
       263 天前
    @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  
       262 天前
    @llej #6 其实都上了依赖注入,最好就是跟着用类去写,同时继承接口的写法也更易懂,而且这样符合依赖倒置原则(高层模块不应依赖于低层模块,二者应依赖于抽象)方便代码的解耦。
    给你看示例代码感受一下:



    而且我可以通过继承 LocalStorageService 这个类,来增加更多额外的功能。当然也可以控制继承的嵌套,我觉得用函数来写的话有点没有那么容易理解
    zhuangzhuang1988
        21
    zhuangzhuang1988  
       251 天前
    @runlongyao2 真复杂的项目还是要的 比如说 vscode
    https://github.com/microsoft/vscode/tree/main/src/vs/platform/instantiation/common
    这是 vscode 的 IOC 实现,整个 vscode 都依赖
    https://github.com/microsoft/vscode/blob/main/src/vs/workbench/browser/web.main.ts#L271 各种服务注册
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2471 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 01:46 PVG 09:46 LAX 18:46 JFK 21:46
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86