请教 golang 依赖注入的实际问题

27 天前
 chaleaochexist

我有一个 task 通过 ssh 运行 N 种命令, 假设 ls -lcat /etc/host吧. 然后把输出存起来.

理想中的结构是:

task 依赖 handler 依赖 sshclient

其中,

handler 是函数, 参数是 sshclient, 一个 handler 执行一种命令

sshclient 是接口, 干活的.

这个 sshclient 实例化过程只能在 task 中动态生成, 因为 sshclient 需要的 ip 是在 task 中的其他函数获取的.

我得问题:

  1. task 依赖 handler. 但是 handler 的参数的类型(也就是 sshclient) 定义在 task 中, 这不循环导入了吗?

目前的解决方案是, 我在 handler 和 task 中分别定义两个一模一样的接口, 然后通过适配的方式能让代码运行. 我不确定这样处理是否合理? 还是说我这个设计本身就有问题? 通过注入接口能实现吗?

  1. 当一个接口的实现的依赖是动态数据的时候, (譬如 sshclient 中的 ip, 端口, 认证信息), 还需要注入吗? 如何注入? 我目前采用的方案是注入一个无参的返回值是工厂函数的函数...然后再 task 中实例化 sshclient. 补充: sshclient 可能有 100 个, 不是一个固定的 client. 说白了我是通过 ssh 采集信息的. 和 *db.DB 不是一个类型.

目前有点混乱, 如果我没问清楚欢迎各位大佬提出你的疑问 我尽量补充信息.

2275 次点击
所在节点    Go 编程语言
44 条回复
sunny352787
27 天前
参数类型为啥定义在 task ?不是应该定义在 handler 吗?
Dorathea
27 天前
"因为 sshclient 需要的 ip 是在 task 中的其他函数获取的"
能否将这个依赖剥离出来, 作为 sshclient 的依赖呢? 如果 task 也需要这个, 让 task 也依赖就好了.

以及"循环导入" 的问题, 我之前接触过 nestjs, 也好奇过循环依赖如何解决
https://docs.nestjs.com/fundamentals/circular-dependency
或许可以另一种思路参考
NessajCN
27 天前
「这个 sshclient 实例化过程只能在 task 中动态生成, 因为 sshclient 需要的 ip 是在 task 中的其他函数获取的.」

没看懂这前后两句话的因果

你建一个 sshclient 的实例,然后在 task 的时候调不就行了。把 ip 当作参数传进去
譬如

sc := NewSshClient()

func task(client *SshClient) {
ip := GetIp()
client.connect(ip)
}

task(&sc)
chaleaochexist
27 天前
@NessajCN 因为注入嘛, 我在注入的时候(也就是初始化的时候) 是不知道 ip 的.

我现在的代码和你的例子差不多, 只不过不是用的 connect 而是注入了一个无参的函数, 这个函数返回一个工厂函数, 这个工厂函数返回 sshclient.

如果 sshclient 的 ip 是静态的类似 db.DB, 直接定义在 handler 和 task 的公共底层. 注入即可, 就没有这么多麻烦事了.
sthwrong
27 天前
task 没有 sshclient 的依赖,依赖是在 handler 中,task 只需要提供参数给 handler 就行了,task 依赖一个工厂函数或者工厂接口就行。
NessajCN
27 天前
@chaleaochexist 你直接发代码吧,我怀疑你提了个 xy 问题,
也许你的原始需求有更直接简单的解决法而不用注入来注入去
iseki
27 天前
要不要在这里叠加 factory 看你的倾向和你使用的 di 框架的能力啊。有的时候工厂是个很简单的解决方案,特别是你想更精确地控制生命周期时。
iseki
27 天前
我虽然不是很确信你的业务,不过如果按照我对你业务的猜测,这里使用 factory pattern 是正确的。
Sendya
27 天前
我感觉直接 interface ,然后套娃引用就行了

https://go.dev/play/p/sa9s8QpKE29
kfpenn
27 天前
我也有这种嵌套的依赖,最后是用 interface 解决的
darksword21
27 天前
op 最好能给出现在的伪代码或者哪个循环引用的伪代码
everhythm
27 天前
lz 不如画图说明下设想的流程和调用逻辑,这里 handler 和 task 名称和职责感觉不清楚

假设是 handler 生成 task ,task 调用 sshclient 干活

1. handler 没有直接管 sshclient
2. 没有看懂
bli22ard
27 天前
type CommandRunner interface {
exec(cmd string) (ret string, err error)
}
chaleaochexist
26 天前
@everhythm
@darksword21
@Sendya
@iseki
@NessajCN
@sthwrong
@sunny352787
@Dorathea
@NessajCN

谢谢你们的回复, 我写了一个 伪代码, 希望可以把问题解释清楚.

https://github.com/chaleaoch/golang_demo.git

一共是三个版本 v1, v2, v3 我把问题都放到了注释中, 可以搜索关键字 "问题" . 各个版本之间的主要差别, 可以搜索关键字 "修改"

如果我得代码有其他的地方的写法上的问题, 随意批评指导, 谢谢你们. 谢谢!!
NessajCN
26 天前
@chaleaochexist 你完全搞错 interface 的用法了,
go 的 interface 跟 jvav 的不是一回事,
你定义了一大堆完全永不上的 interface

go 的 interface 可以类比 python 的 protocol 或 rust 的 trait,
是为了方便你写「非特定类型参数」的函数,或者早期的泛型来用的。
具体的用法是你定义一个 struct 和 interface , 并给 struct 实现 interface 里的函数,
之后你定义的参数为该 interface 的函数就可以直接传这个 struct 的实例了

我的建议还是如 3# 时候讲的,你初始化就初始化 client 然后传引用进 task 函数就好,初始化时候也根本不需要知道 ip

type SSHClient struct {}
func (s *SSHClient) ExecuteCommand(user string, pass string, host string, cmd string) {
// blablabla
}

// 初始化 sshclient 放到 main 函数里, 然后传给下面的 task

func (q *DemoTask) Run(sshClient *SSHClient) {
mIps, _ := q.mIPRepo.GetByType("exampleType") // 这一条也可以大幅简化
for _, mIp := range mIps {
out, _ := sshClient.ExecuteCommand(mIp.Username, mIp.Password, mIp.Ip+":"+mIp.Port, "exampleCommand")
fmt.Println(out)
}
}
chaleaochexist
26 天前
@NessajCN 像你这么写, 最大的问题是如何 mock?

无论是 GetByType 是查询数据库的, 如果不做成接口, 如何 mock
ssh 也是, 在单元测试的时候, 我不希望真的去 ssh 执行一条命令.
NessajCN
26 天前
@chaleaochexist 就像我上一个回复内容提到的,你先要弄明白 go 的 interface 究竟是啥。
先把 jvav 思维彻底舍弃才好跟你讲下一步
chaleaochexist
26 天前
无论是 --> ~~无论是~~
chaleaochexist
26 天前
@NessajCN 好吧...其实我根本就不会 java...
golang 如果不通过 interface 没法 mock.

或者 sshClient 这个接口 可以有多个实现. 我希望注入而不是将 NewsshClient1 改成 NewsshClient2
NessajCN
26 天前
@chaleaochexist 都说了接口不是这么用.....
sshClient 显然是个实例,咋会是接口呢。
go 的 interface 是在定义函数时候作为「类泛型」传参用的,你的 sshClient 必然是要初始化然后调方法的,
概念风马牛不相及呀

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1151824

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX