Wire中文文档:Wire教程
Wire教程
让我们从示例开始学习Wire的使用。Wire指南提供了指引使用该工具的完整文档。对于那些期望看到将Wire应用于更大场景的读者,基于Go Cloud的留言本示例使用Wire来初始化其组件。在这里我们将构建一个小型的迎宾程序用于理解如何使用Wire。最终完成的作品可以在Wire教程目录中找到。
建立迎宾程序的第一关
让我们创建一个小程序来模拟一个带有迎宾员的事件,该迎宾员用特定的信息向客人致意。
起手,我们将创建3种类型:1)给迎宾员的信息 ,2)传达这条消息的迎宾员,3)以迎宾员问候客人开始的活动。在这个设计中,我们得到三种结构体:
type Message string
type Greeter struct {
// ... TBD
}
type Event struct {
// ... TBD
}
Message
类型只是包装了一个字符串。现在,我们将创建一个简单的初始化器,它总是返回一条硬编码的消息:
func NewMessage() Message {
return Message("Hi there!")
}
我们的迎宾员
需要参考那条消息。所以,让我们也为迎宾员
创建一个初始化器。
func NewGreeter(m Message) Greeter {
return Greeter{Message: m}
}
type Greeter struct {
Message Message // <- 添加消息属性
}
在初始化器中,我们将一个Message
消息赋值给Greeter
迎宾员。现在,我们可以在Greeter
上创建Greet
方法时使用Message
。
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
}
然后我们就可以添加一个方法来启动Event
:
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使组件初始化过程更顺利。
让我们开始把main
函数修改成下面这样:
func main() {
e := InitializeEvent()
e.Start()
}
接着,在一个名为wire.go
的单独文件中,我们将定义InitializeEvent
。这就是事情变得有趣的地方:
// wire.go
func InitializeEvent() Event {
wire.Build(NewEvent, NewGreeter, NewMessage)
return Event{}
}
相较于经历依次初始化每个组件并将其传递给下一个组件的麻烦,我们只需要单独调用一个wire.Build方法就可以传递任何我们想要使用的初始化器。在Wire中, 在Wire中,初始化器被称为“提供程序”,即提供特定类型的函数。为了满足编译器的要求,我们为Event
作为返回值。请注意,即使我们向Event
添加值,Wire也会忽略它们。
事实上,注入器的目的是提供关于使用哪个提供商来构造Event的信息,因此我们将在文件顶部的构建约束中从最终的二进制文件中排除它:
//+build wireinject
注意,构建约束需要一个空白的末尾行。
在wire的用法中,InitializeEvent
是一个注入器,现在我们就完成了注入器,我们已经准备好使用wire的注入器。使用以下参数安装工具:
go install github.com/google/wire/cmd/wire@latest
然后在与上述代码相同的目录中,简单滴运行wire
。Wire
将找到InitializeEvent
注入器,并生成一个函数,它的函数体中填满了所有必要的初始化步骤。结果将被写入一个名为wire_gen.go
的文件.
让我们看以下Wire都为我们做了什么:
// wire_gen.go
func InitializeEvent() Event {
message := NewMessage()
greeter := NewGreeter(message)
event := NewEvent(greeter)
return event
}
它和我们上面写的一样!这是一个只有三个组成部分的简单例子, 所以手工编写初始化式并不是很痛苦,
想象一下,对于复杂得多的组件,Wire是多么有用。在使用Wire时,我们需要同时将wire.go
和wire_gin.go
加入到版本控制。
使用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
}
我们会说,有时一个Greeter
可能脾气暴躁,所以我们不能创建一个Event
。NewGreeter
的 initializer
式现在看起来像这样:
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时间戳开始的偶数秒,我们将创建一个Grumpy greeting
,而不是一个友好的greeting
。
Greet
方法就变成了:
func (g Greeter) Greet() Message {
if g.Grumpy {
return Message("Go away!")
}
return g.Message
}
现在你明白了,一个脾气暴躁的Gtreeter
对一个Event
是没有好处的。所以NewEvent
会失败。
main
函数现在必须考虑到InitializeEvent
实际上可能会失败:
func main() {
e, err := InitializeEvent()
if err != nil {
fmt.Printf("failed to create event: %s\n", err)
os.Exit(2)
}
e.Start()
}
我们还需要更新InitializeEvent
,为返回值添加一个error
类型:
// wire.go
func InitializeEvent() (Event, error) {
wire.Build(NewEvent, NewGreeter, NewMessage)
return Event{}, nil
}
设置完成后,我们就可以再次调用Wire
命令了。注意,在运行wire
一次后产生了wire_gen.go
文件,我们也可以用Go生成。运行该命令后,我们的wire_gen.go
文件看起来像这样:
// wire_gen.go
func InitializeEvent() (Event, error) {
message := NewMessage()
greeter := NewGreeter(message)
event, err := NewEvent(greeter)
if err != nil {
return Event{}, err
}
return event, nil
}
Wire
检测到NewEvent
提供程序可能会失败,并在生成的代码中做了正确的事情:它检查错误并在出现错误时提前返回.
更改注入器签名
另一个改善,让我们看一下Wire是如何基于注入器的签名生成代码的。现在我们已经在NewMessage
中硬编码了消息,在实践中,允许调用者以他们认为合适的方式更改消息要好得多。所以让我们将InitializeEvent
更改为如下所示:
func InitializeEvent(phrase string) (Event, error) {
wire.Build(NewEvent, NewGreeter, NewMessage)
return Event{}, nil
}
现在InitializeEvent
允许调用者传入phrase
供Greeter
使用。我们也向NewMessage
添加了一个phrase
请求参数。
func NewMessage(phrase string) Message {
return Message(phrase)
}
在我们重新运行wire
后,我们将看到该工具生成了一个初始化器,它将phrase
作为Message
传递给Greeter
。整洁!
// 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检查注入器的参数,看到我们向参数列表中添加了一个字符串(例如,Phras
),同样地,看到在所有提供程序中,NewMessage
接受一个字符串,因此它将短语传递给NewMessage
。
用有用的Errors捕获错误
让我们看看当Wire检测到代码中的错误时会发生什么,并看看Wire的错误消息如何帮助我们纠正任何问题。
例如,在编写我们的注入器InitializeEvent
时,假设我们忘记为Greeter
添加一个提供商。让我们看看会发生什么:
func InitializeEvent(phrase string) (Event, error) {
wire.Build(NewEvent, NewMessage) // woops! We forgot to add a provider for Greeter
return Event{}, nil
}
运行wire
,我们将看到下面的:
# wrapping the error across lines for readability$GOPATH/src/github.com/google/wire/_tutorial/wire.go:24:1:
inject InitializeEvent: no provider found for github.com/google/wire/_tutorial.Greeter
(required by provider of github.com/google/wire/_tutorial.Event)
wire: generate failed
wire会告诉我们一些有用的消息:它无法为Greeter
找到一个提供者。注意,错误消息打印出Greeter
类型的完整路径。它还告诉我们发生问题的行号和注入器名称:InitializeEvent
中的第24。此外,错误消息告诉我们哪个提供者需要Greeter
。它是Event
类型。一旦我们通过了Greeter
的提供者,问题就解决了。
或者,如果我们提供了太多的提供者而无法进行wire.Build
,会发生什么情况?
func NewEventNumber() int {
return 1
}
func InitializeEvent(phrase string) (Event, error) {
// woops! NewEventNumber is unused.
wire.Build(NewEvent, NewGreeter, NewMessage, NewEventNumber)
return Event{}, nil
}
Wire很有帮助地告诉我们有一个未使用的提供者:
$GOPATH/src/github.com/google/wire/_tutorial/wire.go:24:1:
inject InitializeEvent: unused provider "NewEventNumber"
wire: generate failed
从连接调用中删除未使用的提供程序。生成将解决此错误。
总结
让我们总结以下我们在这里所做的。首先,我们编写了一些带有相应初始化器的组件,或提供者。接下来,我们创建了一个注入器函数,指定它接收哪些参数和返回哪些类型。然后,我们用一个Wire调用来填充注入器函数。构建时提供所有必要的提供程序。最后,我们运行wire命令来生成连接所有不同初始化器的代码。
当我们向注入器添加一个参数和一个错误返回值时,再次运行wire对我们生成的代码进行了所有必要的更新。
这里的示例很小,但它展示了Wire的一些强大功能,以及它如何消除使用依赖项注入初始化代码的许多麻烦。此外,使用Wire产生的代码看起来很像我们在其他情况下编写的代码。没有将用户提交到Wire的定制类型。相反,它只是生成的代码。我们想怎么用就怎么用。最后,另一个值得考虑的问题是,向组件初始化添加新的依赖项是多么容易。只要告诉Wire如何提供(即初始化)一个组件,我们就可以将该组件添加到
最后,值得一提的是,Wire支持许多这里没有讨论的附加特性。提供程序可以分组在提供程序集中。它支持绑定接口、绑定值以及支持清理函数。有关更多信息,请参阅高级功能部分
发表回复