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

然后在与上述代码相同的目录中,简单滴运行wireWire将找到InitializeEvent注入器,并生成一个函数,它的函数体中填满了所有必要的初始化步骤。结果将被写入一个名为wire_gen.go的文件.

让我们看以下Wire都为我们做了什么:

// wire_gen.go

func InitializeEvent() Event {
    message := NewMessage()
    greeter := NewGreeter(message)
    event := NewEvent(greeter)
    return event
}

它和我们上面写的一样!这是一个只有三个组成部分的简单例子, 所以手工编写初始化式并不是很痛苦,
想象一下,对于复杂得多的组件,Wire是多么有用。在使用Wire时,我们需要同时将wire.gowire_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可能脾气暴躁,所以我们不能创建一个EventNewGreeter 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允许调用者传入phraseGreeter使用。我们也向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支持许多这里没有讨论的附加特性。提供程序可以分组在提供程序集中。它支持绑定接口绑定值以及支持清理函数。有关更多信息,请参阅高级功能部分

发表回复

您的电子邮箱地址不会被公开。