Wire 使用教程
Wire 是 Google 提供的帮助 Go 开发人员实现编译时依赖注入的工具.
通过例子学习使用 Wire. 这里我们要建立一个小的欢迎程序, 用来了解如何使用 Wire.
构建欢迎程序的第一步
让我们创建一个小程序, 它模拟一个事件, 其中包含一个带有特定消息的欢迎来宾的问候语.
我们创建了三种数据类型
给迎宾员的信息
传达信息的迎宾员
以迎宾开始的活动
- type Message string
- type Greeter struct {
- // ... TBD
- }
- type Event struct {
- // ... TBD
- }
现在, 我们将创建一个简单的初始化器, 总是返回一个硬编码的消息:
- func NewMessage() Message {
- return Message("Hi there!")
- }
我们的迎宾员需要引用这条消息, 我们也为欢迎者创建一个初始化器.
- func NewGreeter(m Message) Greeter {
- return Greeter{Message: m}
- }
- type Greeter struct {
- Message Message // <- adding a Message field
- }
在初始化器中, 我们给 Greeter 分配了一个 Message 字段. 现在, 当我们在 Greeter 上创建一个 Greet 方法时, 我们可以使用这个消息:
- func (g Greeter) Greet() Message {
- return g.Message
- }
接下来, Event 需要有一个 Greeter, 所以我们也会为它创建一个初始化器.
- func NewEvent(g Greeter) Event {
- return Event{Greeter: g}
- }
- type Event struct {
- Greeter Greeter // <- adding a Greeter field
- }
然后我们添加一个方法来启动事件:
- func (e Event) Start() {
- msg := e.Greeter.Greet()
- fmt.Println(msg)
- }
Start 方法是我们这个小应用程序的核心: 它告诉欢迎者发出一个问候语, 然后将该消息打印到屏幕上.
现在我们已经准备好了应用程序的所有组件, 让我们看看在不使用 Wire 的情况下初始化所有组件需要做些什么. 我们的主函数是这样的:
- func main() {
- message := NewMessage()
- greeter := NewGreeter(message)
- event := NewEvent(greeter)
- event.Start()
- }
首先我们创建一个消息, 然后用这个消息创建一个欢迎器, 最后我们用这个欢迎器创建一个事件. 完成所有初始化之后, 我们就可以开始事件了.
我们使用依赖注入设计原则. 实际上, 这意味着我们传递每个组件需要的任何内容. 这种设计风格有助于编写易于测试的代码, 并使一种依赖关系与另一种依赖关系的交换变得容易.
使用 Wire 生成代码
依赖项注入的一个缺点是需要如此多的初始化步骤. 让我们看看如何使用 Wire 使初始化组件的过程更顺畅.
我们先简化我们的主函数:
- func main() {
- e := InitializeEvent()
- e.Start()
- }
接下来, 在一个名为 wire 的单独文件中. 我们将定义 InitializeEvent. 这就是事情变得有趣的地方:
- // wire.go
- func InitializeEvent() Event {
- wire.Build(NewEvent, NewGreeter, NewMessage)
- return Event{}
- }
与其依次初始化每个组件并将其传递给下一个组件, 不如使用一个连接调用. 构建传递我们想要使用的初始化器. 在 Wire 中初始化器被称为 "提供者", 即提供特定类型的函数. 我们为事件添加一个零值作为返回值, 以满足编译器的要求. 注意, 即使我们向事件添加值, Wire 也会忽略它们. 事实上, 注入器的目的是提供关于使用哪些提供者来构造一个事件的信息, 因此我们将在文件顶部的 build 约束中把它从最终的二进制代码中排除掉:
- //+build wireinject
- # 类似
- //+build wireinject
- // The build tag makes sure the stub is not built in the final build.
- package main
InitializeEvent 是一个 "注入器". 现在我们已经完成了注入器, 可以使用 wire 命令行工具了.
- # 安装工具
- go get GitHub.com/google/wire/cmd/wire
然后在与上述代码相同的目录中, 简单地运行 wire.Wire 将找到 InitializeEvent 注入器并生成一个函数, 其主体由所有必要的初始化步骤填充. 生成文件为 wire_gen.go
- // wire_gen.go
- func InitializeEvent() Event {
- message := NewMessage()
- greeter := NewGreeter(message)
- event := NewEvent(greeter)
- return event
- }
它看起来就像我们上面写的一样! 现在, 这是一个只有三个组件的简单示例, 因此手工编写初始化器并不太困难. 想象一下, 对于复杂得多的组件, Wire 是多么有用. 当使用 Wire 时, 我们将提交两个 Wire. 去 wire_gen. 转到源代码控制.
使用 Wire 进行更改
为了演示 Wire 如何处理更复杂的设置, 让我们重构 Event 的初始化器, 以返回一个错误, 然后看看会发生什么.
- func NewEvent(g Greeter) (Event, error) {
- if g.Grumpy {
- return Event{}, errors.New("could not create event: event greeter is grumpy")
- }
- return Event{Greeter: g}, nil
- }
我们会说有时候一个迎宾员可能脾气暴躁, 所以我们不能创建一个事件. NewGreeter 的初始化现在看起来是这样的:
- func NewGreeter(m Message) Greeter {
- var grumpy bool
- if time.Now().Unix()%2 == 0 {
- grumpy = true
- }
- return Greeter{Message: m, Grumpy: grumpy}
- }
我们已经向 Greeter 结构中添加了一个 grumpy 的字段, 如果初始化器的调用时间与 Unix 时代相比是偶数秒, 那么我们将创建一个暴躁的 Greeter, 而不是一个友好的 Greeter.
- func (g Greeter) Greet() Message {
- if g.Grumpy {
- return Message("Go away!")
- }
- return g.Message
- }
现在你明白了, 一个脾气暴躁的迎宾员对一件事是多么地不利. 因此 NewEvent 可能会失败. 我们的主现在必须考虑到 InitializeEvent 可能会失败:
- func main() {
- e, err := InitializeEvent()
- if err != nil {
- fmt.Printf("failed to create event: %s\n", err)
- os.Exit(2)
- }
- e.Start()
- }
我们还需要更新 InitializeEvent, 将错误类型添加到返回值:
- // wire.go
- func InitializeEvent() (Event, error) {
- wire.Build(NewEvent, NewGreeter, NewMessage)
- return Event{}, nil
- }
再次运行 wire 生成代码, 如下
- // wire_gen.go
- func InitializeEvent(phrase string) (Event, error) {
- message := NewMessage(phrase)
- greeter := NewGreeter(message)
- event, err := NewEvent(greeter)
- if err != nil {
- return Event{}, err
- }
- return event, nil
- }
Wire 检查注入器的参数, 看到我们向参数列表中添加了一个字符串 (例如, phrase), 同样看到在所有提供程序中, NewMessage 接受一个字符串, 因此它将 phrase 传递给 NewMessage.
完整的代码
来源: http://www.tuicool.com/articles/UJfQ32M