当大潮退去,才知道谁在裸泳。
作者:A哥(YourBatman)
公众号:BAT的乌托邦(ID:BAT-utopia)
文末是否有彩蛋:有
各位小伙伴大家好,我是A哥。以下文章来源于硅谷成长攻略 ,作者大西Xi。
依赖注入,它不仅仅是Spring,而是一种通用思想。本文硅谷大佬用简短的语句道出了其核心思想,值得参阅。
我最近在给一个Go service升级重构framework。我和一个朋友提了下,他点评到,搞这种基础升级,就是悟道啊,类似于《禅与摩托车维修艺术》。
他这个说法挺有道理的,大家平时写业务代码,更多是站在地面想着怎么快速完成目标。只有趁升级的时候,才有空飞在1000公里天上,想想为啥要这么设计的哲学问题。
今天就给大家介绍一个重要的基本设计原则:Dependency Injection。这个设计模式在复杂的业务service非常有用,没有它,每次改一个模块的初始化接口,你都要把用到这个模块的代码都改一遍,非常麻烦。
今天很多主流的开源framework都用到了它,比如:
这个翻译成中文叫做依赖注入,用大白话解释就是即插即用。
举个例子,假设你的service里面有个模块A叫“笔记本”,它有个依赖叫“耳机”,用了这个设计原则,你需要听音乐,只用插”耳机“就可以了。后端service中常见的“耳机”依赖有哪些?比如Logging,输出Metrics等。
下面的代码是用 Dependency Injection 创建模块A的伪代码:
func CreateLaptopService() *LaptopService {
panic(wire.Build(
wire.Struct(new(Logger), "*"),
NewHttpClient,
NewHeadphoneService,
))
}
你需要自己搞一堆耳机的原材料,然后自己组装配置。模块A需要耳机的时候,手动装一遍,模块B需要耳机的时候,再手动装一遍。
下面是不用Dependency Injection,创建模块A的伪代码:
func CreateLaptopService() *LaptopService {
logger := &Logger{}
headphone := &Headphone{}
client := NewHttpClient(logger)
return NewLaptopService(logger,client,headphone)
}
如果service很简单,还可以忍受。但是在业务很复杂时,项目里有上百个依赖的时候就更痛苦了。每次配置”耳机“,你都需要手动把所有模块的接口配置一遍。
下面是不用Dependency Injection,再创建模块B的伪代码:
func CreateDesktopService() *DesktopService {
logger := &Logger{}
headphone := &Headphone{}
client := NewHttpClient(logger)
cdDisk := &CdDisk{}
cdDrive := &CdDrive{cdDisk}
headphone := &Headphone{}
return NewLaptopService(logger, client, headphone, cdDrive)
}
有了Dependency Injection,每次配置时,模块A和模块B都是连接到同一个设置的耳机,你只要组装一次耳机。即使有100个模块都需要用耳机,你也只需要组装一次。
而对于更复杂的场景,模块B依赖于一个”CD机“,而”CD机“又需要一个”CD碟片“。如果有100个类似的模块都有”CD机“,而你需要做的只是更改”CD机”里的CD碟片,有了Dependency Injection,你也可以省去在“电子厂”里面翻找所有”CD机“的时间,只需要换一张”CD碟片“。
每次升级时,只需要测试”耳机“本身的性能,测试不需要和使用”耳机“的代码有任何关联。
最后,划一下重点,Dependency Injection适用的场景,是复杂的大型系统,有很多个服务相互依赖的情况。它能够避免一些重复劳动带来的小错误,提高生产力。如果是一个人写的小玩具,那杀鸡就不用牛刀啦。