惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

aimingoo的专栏
aimingoo的专栏
S
Securelist
博客园 - Franky
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
IT之家
IT之家
GbyAI
GbyAI
Microsoft Azure Blog
Microsoft Azure Blog
The Cloudflare Blog
云风的 BLOG
云风的 BLOG
N
News and Events Feed by Topic
AI
AI
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Schneier on Security
Schneier on Security
Attack and Defense Labs
Attack and Defense Labs
Vercel News
Vercel News
腾讯CDC
Google DeepMind News
Google DeepMind News
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
M
MIT News - Artificial intelligence
WordPress大学
WordPress大学
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
N
Netflix TechBlog - Medium
量子位
S
Schneier on Security
Hacker News: Ask HN
Hacker News: Ask HN
Cyberwarzone
Cyberwarzone
S
Security Affairs
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
N
News and Events Feed by Topic
T
Tenable Blog
PCI Perspectives
PCI Perspectives
MyScale Blog
MyScale Blog
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Cyber Attacks, Cyber Crime and Cyber Security
W
WeLiveSecurity
N
News | PayPal Newsroom
P
Proofpoint News Feed
O
OpenAI News
C
CERT Recently Published Vulnerability Notes
B
Blog
Cisco Talos Blog
Cisco Talos Blog
Microsoft Security Blog
Microsoft Security Blog
V
Visual Studio Blog
MongoDB | Blog
MongoDB | Blog
大猫的无限游戏
大猫的无限游戏
A
Arctic Wolf
Y
Y Combinator Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Spread Privacy
Spread Privacy

ZDDHUB

PixelsMeasure 开发第二年总结 PixelsMeasure 开发一年总结 Swift/SwiftUI 踩坑记 为什么说 GPT 利好程序员 ChatGPT 编程实现 Web 数字水印 Web 数字水印探究 Micro Frontends for Mobile URL 加载系统(URL Loading System) Protocol Buffers GraphQL 从 0 到 1 开发一款 IOS 应用 - Swift MV* 软件设计架构 学习一个新技巧需要多久? 不停机数据库迁移 Rspec 如何 mock update 方法更新自己? Rails 使用 mysql2 出现的段错误 使用 Docker-compose 部署 Rails 应用到生产环境 Cocoa troubleshooting 独孤九剑 Dit (0x05) - 终端篇 Gem-based Jekyll theme 开发小记 Miscellaneous 前端手记 TodoMVC 之 Redux 篇 前端手记 TodoMVC 之 Server 篇 前端手记 TodoMVC 之 React 篇 前端手记 TodoMVC 之 CSS 篇 独孤九剑 Dit (0x04) - 测试篇 独孤九剑 Dit (0x03) - 缓存篇 英语小抄 LLDB debug Golang Make mistakes 大牛俱乐部上线啦 独孤九剑 Dit (0x02) - 数据结构篇 独孤九剑 Dit (0x01) - 总决 独孤九剑 Dit (0x00) - 我为什么要做 Dit 零值强制类型转换的使用 终端颜色输出重定向 Go语法简略 - 正则表达式 Makefile Go语法简略 - Duck框架探索 Go语法简略 - web应用框架 Go语法简略 - 反射 Go语法简略 - 面向对象 Go语法简略 - goroutine Go语法简略 - 方法和接口 Go语法简略 - 基础篇 大牛 轻松科研 未来这几年 为 Android Studio 创建图标 Shell Git Vim 金庸答百问 论拖延症 Flex, A fast scanner generator 有理想的人 从虚拟到现实 常用视频转接口 Recognizer configuration on CentOS 整个世界清静了 《Python源码剖析》读书笔记
Go语法简略 - 依赖注入
zddhub · 2015-07-05 · via ZDDHUB

通过对web应用框架背后原理的探索,引入了依赖注入的概念。如果你需要读懂或者写一个框架的话,依赖注入的思想绝对能帮到你。本文记录对依赖注入的探索。

区分依赖和宿主

现在我们把问题简化一下:

package main

import "fmt"

func MethodA(name string, f func(a int, b string)) {
  fmt.Println("Enter MethodA:", name)
  f(3030, "zdd") // 给f注入参数
  fmt.Println("Exit MethodA:", name)
}

func MethodB(a int, b string) {
  fmt.Println(a, b)
}

func main() {
  d := MethodB
  MethodA("zddhub", d)
}

在上述的例子中,为了完成MethodA的功能,需要依赖MethodB方法的实现,把MethodB方法叫做MethodA的依赖(Dependency), MethodA叫做宿主(Host)。上面的例子直接在Host中调用f(3030, "zdd")来给依赖注入参数。或者换一个角度理解,将自定义的方法MethodB整体注入到MethodA的执行环境中。为了通用性考虑,通常会实现一个依赖注入器。

依赖注入器

接下来实现一个依赖注入器(Injector)。在web应用框架的例子中,我们发现,在Host中,只能拿到依赖方法的参数类型,而我们需要将参数类型和参数值建立联系,因此,在Injector中封装一个map[Type]Value是自然而然的选择。

package main

import (
  "fmt"
  "reflect"
)

type Injector struct {
  mappers map[reflect.Type]reflect.Value // 根据类型map实际的值
}

func (inj *Injector) SetMap(value interface{}) {
  inj.mappers[reflect.TypeOf(value)] = reflect.ValueOf(value)
}

func (inj *Injector) Get(t reflect.Type) reflect.Value {
  return inj.mappers[t]
}

func (inj *Injector) Invoke(i interface{}) []reflect.Value {
  t := reflect.TypeOf(i)
  if t.Kind() != reflect.Func {
    panic("Should invoke a function!")
  }
  inValues := make([]reflect.Value, t.NumIn())
  for k := 0; k < t.NumIn(); k++ {
    inValues[k] = inj.Get(t.In(k))
  }
  ret := reflect.ValueOf(i).Call(inValues)
  return ret
}

func Host(name string, f func(a int, b string)) {
  fmt.Println("Enter Host:", name)

  inj.Invoke(f) // 利用注入器中的环境调用f
  // 这种使用方法,看起来就像把自定义的方法f里的执行语句放在Host中执行一样自然
  // 语句从f里穿透到Host方法中,这就是注入一词的由来。

  fmt.Println("Exit Host:", name)
}

func Dependency(a int, b string) {
  fmt.Println("Dependency: ", a, b)
}

var inj *Injector // 全局的注入器,保存注入环境

func main() {
  // 创建注入器
  inj = &Injector{make(map[reflect.Type]reflect.Value)}
  inj.SetMap(3030)
  inj.SetMap("zdd")

  d := Dependency
  Host("zddhub", d)

  inj.SetMap(8080)
  inj.SetMap("www.zddhub.com")
  Host("website", d)
}

这样,一个简易的注入器就完成了。突然发现它和装饰器有那么一丢丢的像呀。不断的在外围裹上一层层的衣服,而你所需要专心关注的,正是衣服里面的😜,啊哈哈。。。😄。

稍微改进一下我们的注入器,让它更通用些。

package main

import (
  "fmt"
  "reflect"
)

type Injector struct {
  mappers map[reflect.Type]reflect.Value // 根据类型map实际的值
}

func (inj *Injector) SetMap(value interface{}) {
  inj.mappers[reflect.TypeOf(value)] = reflect.ValueOf(value)
}

func (inj *Injector) Get(t reflect.Type) reflect.Value {
  return inj.mappers[t]
}

func (inj *Injector) Invoke(i interface{}) []reflect.Value {
  t := reflect.TypeOf(i)
  if t.Kind() != reflect.Func {
    panic("Should invoke a function!")
  }
  inValues := make([]reflect.Value, t.NumIn())
  for k := 0; k < t.NumIn(); k++ {
    inValues[k] = inj.Get(t.In(k))
  }
  ret := reflect.ValueOf(i).Call(inValues)
  return ret
}

func New() *Injector {
  return &Injector{make(map[reflect.Type]reflect.Value)}
}

func Host(name string, i interface{}) { // 让注入的方法不受限制
  inj.Invoke(i) // 利用注入器中的环境调用f
}

func Dependency(a int, b string) {
  fmt.Println("Dependency: ", a, b)
}

var inj *Injector

func main() {
  inj = New()
  inj.SetMap(3030)
  inj.SetMap("zdd")

  d := Dependency
  Host("zddhub", d)

  inj.SetMap(8080)
  inj.SetMap("zddhub@gmail.com")
  inj.SetMap(10.24)

  Host("email", func(email string, money float64, number int) {
    fmt.Println(email, money, number)
  })

  Host("Get", func() {
    fmt.Println("Hello world!")
  })
}

依赖注入的另一种使用场景

依赖注入的另一种使用方法是用在依赖属性的构造上,通常在测试框架中应用广泛。通过下面的例子,理解一下吧。

type Camera struct { // 定义一个单反相机
  Name string
  L    Lens
}

type Lens struct { 
  LensType int // 0: 普通镜头,1: 广角镜头,2: 长焦镜头
}

func (c Camera) Capture() { // 使用不同的镜头拍照
  m := make(map[int]string, 3)
  m[0], m[1], m[2] = "normal lens", "wide-angle lens", "telephoto lens"

  value := inj.Get(reflect.TypeOf(c.L))
  var index int
  if value.Kind() != reflect.Invalid {
    index = value.Interface().(Lens).LensType
  } else {
    index = 0
  }
  fmt.Println("capture with", m[index])
}

var inj *Injector // 持有一个全局的注入器,复用之前实现的注入器。

func main() {
  inj = New()

  c := Camera{"ZddCamera", Lens{0}} // 组装一个普通镜头的单反相机, 通常组装一个单反需要很长的时间,假设你有工匠情结,并且DIY成功的概率是100%,努力工作一个月就可以组装完一台。
  c.Capture() // 拍照
  // 如果想继续测试广角镜头和长焦镜头的拍照效果,那么是不是要这样?
  // d := Camera{"ZddCamera", Lens{1}} // 你又努力工作了一个月
  // e := Camera{"ZddCamera", Lens{2}} // 你又努力工作了一个月
  // 如果Boss告诉你,最近又进口了十几款镜头,你是不是感觉要死的心都有了呢。

  // 正常人只需要组装新的镜头
  wLens := Lens{0} // 努力工作十天
  tLens := Lens{1} // 努力工作十天

  inj.SetMap(wLens) // 复用之前的单反,只更换镜头
  c.Capture() // 拍照

  inj.SetMap(tLens) // 复用之前的单反,只更换镜头
  c.Capture() // 拍照

  // 是不是感觉棒棒嗒😄
  //: capture with normal lens
  //: capture with wide-angle lens
  //: capture with normal lens
}

测试框架猜测

比如Android的测试框架通常会出现形如@Inject的注释,它都做了些什么事情呢?下面给出一个完整可运行的例子程序来说明一下:

package main

import (
  "fmt"
  "reflect"
)

type Injector struct {
  mappers map[reflect.Type]reflect.Value // 根据类型map实际的值
}

func (inj *Injector) SetMap(value interface{}) {
  inj.mappers[reflect.TypeOf(value)] = reflect.ValueOf(value)
}

func (inj *Injector) Get(t reflect.Type) reflect.Value {
  return inj.mappers[t]
}

func (inj *Injector) Invoke(i interface{}) []reflect.Value {
  t := reflect.TypeOf(i)
  if t.Kind() != reflect.Func {
    panic("Should invoke a function!")
  }
  inValues := make([]reflect.Value, t.NumIn())
  for k := 0; k < t.NumIn(); k++ {
    inValues[k] = inj.Get(t.In(k))
  }
  ret := reflect.ValueOf(i).Call(inValues)
  return ret
}

func New() *Injector {
  return &Injector{make(map[reflect.Type]reflect.Value)}
}

type Camera struct { // 定义一个单反相机
  Name string
  // @Inject // <--你在这里说明了L是需要注入的,通常用注释来说明注入
  L Lens
}

type Lens struct {
  LensType int // 0: 普通镜头,1: 广角镜头,2: 长焦镜头
}

func (c Camera) Capture() { // 使用不同的镜头拍照
  m := make(map[int]string, 3)
  m[0], m[1], m[2] = "normal lens", "wide-angle lens", "telephoto lens"

  //* fmt.Println("capture with", m[c.L.LensType]) // 这是你在程序中的代码,框架看到你使用了c.L
  // 默默的给你做了如下替换:
  fmt.Println("capture with", m[getLens(c.L).LensType])
}

// 由框架在编译时插入的方法
func getLens(L Lens) Lens {
  value := inj.Get(reflect.TypeOf(L))
  var l Lens
  if value.Kind() != reflect.Invalid {
    l = value.Interface().(Lens)
  } else {
    l = Lens{0}
  }
  return l
}

var inj *Injector // 持有一个全局的注入器,复用之前实现的注入器。

func main() {
  inj = New()

  c := Camera{"ZddCamera", Lens{0}}

  wLens := Lens{1}
  inj.SetMap(wLens) // 你需要用某种方式进行注入

  c.Capture()
}

好了,以上解释了依赖注入的使用和原理,如果你要读懂或者写一个框架的话,反射和依赖注入都是很有用的特征,那就掌握它吧。或者用的时候来读读这篇文章吧。

上述注入器的bug

你一定发现了上述注入器的bug:

func main() {
  inj := New()
  inj.SetMap("zdd")
  inj.SetMap("daniu")
  inj.SetMap("opensse")
  Host("Get", func(a string, b string, c string) {
    fmt.Println("Hello, ", a, b, c)
    // 本来想给三个人say hello,结果发现给最后一个人说了三次
    //: Hello,  opensse opensse opensse
  })

你会发现单用类型做拓扑的注入器,只会持有最新一次类型的值。那么怎么办呢?没有解决不了的问题,传个[]string也是极好的。如果参数的结构比较固定的话,封装成struct也是不错的选择,Go允许给struct打Tag,而reflect也支持对Tag的访问,如果对这部分感兴趣,推荐你去读一下"encoding/xml", "encoding/json"包的源码。

什么? 还有bug!!!, 😱, 留言或者发邮件告诉我吧, 欢迎各种骚扰。另外,赞赏作者里有微信号哦,一般人我不告诉他🍀。

理解依赖注入了吗?现在读odegangsta/inject是不是感觉很溜呀?facebook还开源了一套基于有向图实现的注入器facebookgo/inject感觉屌屌的。如果对Java情有独钟,那就研究一下Dagger吧,相信你一定会有所收获。

最近写 Android 应用程序时用到Butter Knife,再一次体会到依赖注入的用处,并把依赖注入的思想,用在了最近的 C++ 项目中。

如果你喜欢这篇文章,欢迎赞赏作者以示鼓励