知识点
依赖注入举例
首先我们应该明白什么是控制反转 -> 控制反转(IoC)与依赖注入(DI)
控制反转的目的很简单,即”解耦“
下面用一个打印示例来说明:
func Greet(name string) {
fmt.Printf("Hello, %s", name)
}
那么我们该如何测试它呢?调用 fmt.Printf 会打印到标准输出,用测试框架来捕获它会非常困难。
我们所需要做的就是注入(这只是一个等同于「传入」的好听的词)打印的依赖。
我们的函数不需要关心在哪里打印,以及如何打印,所以我们应该接收一个接口,而非一个具体的类型。
如果我们这样做的话,就可以通过改变接口的实现,控制打印的内容,于是就能测试它了。在实际情况中,你可以注入一些写入标准输出的内容。
如果你看看 fmt.Printf 的源码,你可以发现一种引入(hook in)的方式:
// It returns the number of bytes written and any write error encountered.
func Printf(format string, a ...interface{}) (n int, err error) {
return Fprintf(os.Stdout, format, a...)
}
有意思!在 Printf 内部,只是传入 os.Stdout,并调用了 Fprintf。
os.Stdout 究竟是什么?Fprintf 期望第一个参数传递过来什么?
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
p := newPrinter()
p.doPrintf(format, a)
n, err = w.Write(p.buf)
p.free()
return
}
io.Writer 是:
type Writer interface {
Write(p []byte) (n int, err error)
}
如果你写过很多 Go 代码的话,你会发现这个接口出现的频率很高,因为 io.Writer 是一个很好的通用接口,用于「将数据放在某个地方」。
所以我们知道了,在幕后我们其实是用 Writer 来把问候发送到某处。我们现在来使用这个抽象,让我们的代码可以测试,并且重用性更好。
依赖注入的作用
- 测试代码。如果你不能很轻松地测试函数,这通常是因为有依赖硬链接到了函数或全局状态。例如,如果某个服务层使用了全局的数据库连接池,这通常难以测试,并且运行速度会很慢。DI 提倡你注入一个数据库依赖(通过接口),然后就可以在测试中控制你的模拟数据了。
- 关注点分离,解耦了数据到达的地方和如何产生数据。如果你感觉一个方法 / 函数负责太多功能了(生成数据并且写入一个数据库?处理 HTTP 请求并且处理业务级别的逻辑),那么你可能就需要 DI 这个工具了。
- 在不同环境下重用代码。我们的代码所处的第一个「新」环境就是在内部进行测试。但是随后,如果其他人想要用你的代码尝试点新东西,他们只要注入他们自己的依赖就可以了。
引用
欢迎大家关注我的公众号
网友评论