前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Google的 DI 框架 Wire

Google的 DI 框架 Wire

作者头像
Yuyy
发布2022-09-21 10:20:35
6420
发布2022-09-21 10:20:35
举报

前言

以下内容来自 Wire 官方文档,花了一天把英文的 readme 啃了遍,发现存在几个问题:

  1. 记住的不多
  2. 后面遇到问题需要再来看readme,但是个人英语阅读效率太低,又要花很多时间
  3. 将来复习时,去看英文文档,没有中文的直观。

所以在这里记录以下。

介绍

Wire是一个代码生成工具,使用依赖注入自动连接组件。组件之间的依赖关系在Wire中表示为函数参数,鼓励显式初始化,而不是全局变量。因为Wire没有运行时状态或反射,所以编写用于Wire的代码即使对于手工编写的初始化也很有用。

如果使用全局变量,可以在运行期间动态获取到依赖,但会导致依赖关系不固定。

使用示例

未使用依赖注入

模拟一个活动,让迎宾员用一条特定的信息向客人致意。

在本设计中,有三种结构类型:
代码语言:javascript
复制
// 为迎宾者发送的消息
type Message string

// 传达该消息的迎宾者
type Greeter struct {
    Message Message 
}

// 迎宾者说出问候语
func (g Greeter) Greet() Message {
    return g.Message
}

// 以迎宾者问候客人开始的活动
type Event struct {
    Greeter Greeter
}

// 活动开始
func (e Event) Start() {
    msg := e.Greeter.Greet()
    fmt.Println(msg)
}
相应的初始化函数
代码语言:javascript
复制
func NewMessage() Message {
    return Message("Hi there!")
}

func NewGreeter(m Message) Greeter {
    return Greeter{Message: m}
}

func NewEvent(g Greeter) Event {
    return Event{Greeter: g}
}
主函数
代码语言:javascript
复制
func main() {
    message := NewMessage()
    greeter := NewGreeter(message)
    event := NewEvent(greeter)

    event.Start()
}
  • 这是简单示例,如果是实际项目中,依赖关系复杂,这个main函数的内容很难写。

使用 Wire

使用依赖注入设计原则,传递每个组件所需的任何信息。有助于编写易于测试的代码,并且易于替换实现类,提高扩展性。

主函数
代码语言:javascript
复制
func main() {
    e := InitializeEvent()

    e.Start()
}
需要编写的 wire.go
代码语言:javascript
复制
//+build wireinject

func InitializeEvent() Event {
    wire.Build(NewEvent, NewGreeter, NewMessage)
    return Event{}
}
  • //+build wireinject:此文件的目的是说明需要使用哪些提供provider函数,因此需要在文件顶部标识,使用构建约束将其从最终构建后的文件中排除
使用 wire 命令工具
安装

go get github.com/google/wire/cmd/wire

使用
  • wire:生成依赖注入代码wire_gen.go
  • go generate:当存在wire_gen.go时,也就是非初次,还可使用这个命令
生成的依赖注入代码 wire_gen.go
代码语言:javascript
复制
func InitializeEvent() Event {
    message := NewMessage()
    greeter := NewGreeter(message)
    event := NewEvent(greeter)
    return event
}

支持返回异常

代码语言:javascript
复制
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
}
主函数
代码语言:javascript
复制
func main() {
    e, err := InitializeEvent()
    if err != nil {
        fmt.Printf("failed to create event: %s\n", err)
        os.Exit(2)
    }
    e.Start()
}
依赖注入声明函数
代码语言:javascript
复制
func InitializeEvent() (Event, error) {
    wire.Build(NewEvent, NewGreeter, NewMessage)
    return Event{}, nil
}
生成的依赖注入代码
代码语言:javascript
复制
func InitializeEvent() (Event, error) {
    message := NewMessage()
    greeter := NewGreeter(message)
    event, err := NewEvent(greeter)
    if err != nil {
        return Event{}, err
    }
    return event, nil
}

给依赖提供参数

需要参数的依赖
代码语言:javascript
复制
func NewMessage(phrase string) Message {
    return Message(phrase)
}
依赖注入声明函数
代码语言:javascript
复制
func InitializeEvent(phrase string) (Event, error) {
    wire.Build(NewEvent, NewGreeter, NewMessage)
    return Event{}, nil
}
生成的依赖注入代码
代码语言:javascript
复制
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
}

进阶使用

Provider 分组

如果将所有的 Provider 都用到一个依赖注入声明函数里,会很臃肿,不便于管理。可以根据包来对 Provider 进行分组,每个包下都有一个 ProviderSet,使包里的依赖注入更清晰。

子包下的 wire.go
代码语言:javascript
复制
var SuperSet = wire.NewSet(ProvideFoo, ProvideBar, ProvideBaz)
主包下的 wire.go
代码语言:javascript
复制
func initializeBaz(ctx context.Context) (foobarbaz.Baz, error) {
    wire.Build(foobarbaz.SuperSet)
    return foobarbaz.Baz{}, nil
}

接口绑定实现类

接口
代码语言:javascript
复制
type Fooer interface {
    Foo() string
}
实现类
代码语言:javascript
复制
type MyFooer string

func (b *MyFooer) Foo() string {
    return string(*b)
}

func provideMyFooer() *MyFooer {
    b := new(MyFooer)
    *b = "Hello, World!"
    return b
}
依赖接口
代码语言:javascript
复制
type Bar string

func provideBar(f Fooer) string {
    // f will be a *MyFooer.
    return f.Foo()
}
依赖注入声明函数
代码语言:javascript
复制
var Set = wire.NewSet(
    provideMyFooer,
    wire.Bind(new(Fooer), new(*MyFooer)),
    provideBar)

Struct Providers

不提供Struct 的 Provider,直接注入字段

代码语言:javascript
复制
type Foo int
type Bar int

func ProvideFoo() Foo {/* ... */}

func ProvideBar() Bar {/* ... */}

type FooBar struct {
    MyFoo Foo
    MyBar Bar
}

var Set = wire.NewSet(
    ProvideFoo,
    ProvideBar,
    wire.Struct(new(FooBar), "MyFoo", "MyBar"))
注入所有字段

wire.Struct(new(FooBar), "*")

排除个别字段
代码语言:javascript
复制
type Foo struct {
    mu sync.Mutex `wire:"-"`
    Bar Bar
}

提供初始值

这是没有写 Provider 的情况,也可以通过 Provider 来指定初始值

代码语言:javascript
复制
type Foo struct {
    X int
}

func injectFoo() Foo {
    wire.Build(wire.Value(Foo{X: 42}))
    return Foo{}
}
生成的依赖注入代码
代码语言:javascript
复制
func injectFoo() Foo {
    foo := _wireFooValue
    return foo
}

var (
    _wireFooValue = Foo{X: 42}
)
  • 这种方式指定初始值,不能调用任何函数,以及从管道里获取数据
给接口提供初始值
代码语言:javascript
复制
func injectReader() io.Reader {
    wire.Build(wire.InterfaceValue(new(io.Reader), os.Stdin))
    return nil
}

字段作为依赖

代码语言:javascript
复制
type Foo struct {
    S string
    N int
    F float64
}

func getS(foo Foo) string {
    // Bad! Use wire.FieldsOf instead.
    return foo.S
}

func provideFoo() Foo {
    return Foo{ S: "Hello, World!", N: 1, F: 3.14 }
}

func injectedMessage() string {
    wire.Build(
        provideFoo,
        getS)
    return ""
}
使用 wire.FieldsOf()
代码语言:javascript
复制
func injectedMessage() string {
    wire.Build(
        provideFoo,
        wire.FieldsOf(new(Foo), "S"))
    return ""
}
生成的依赖注入代码
代码语言:javascript
复制
func injectedMessage() string {
    foo := provideFoo()
    string2 := foo.S
    return string2
}

提供清理函数

代码语言:javascript
复制
func provideFile(log Logger, path Path) (*os.File, func(), error) {
    f, err := os.Open(string(path))
    if err != nil {
        return nil, nil, err
    }
    cleanup := func() {
        if err := f.Close(); err != nil {
            log.Log(err)
        }
    }
    return f, cleanup, nil
}
  • 这个清理函数是闭包的,供外部调用
  • 如果 Provider 创建了需要清理的值(例如关闭文件),那么它可以返回一个清理函数来清理资源。注入器将使用它向调用者返回聚合清理函数,或者在注入器实现中稍后调用的提供程序返回错误时清理资源。
  • 清理函数保证在此 Provider 的任何依赖的清理函数之前被调用,必须具有签名func()

不 return 最终生成的对象

在依赖注入声明函数中,会返回一个无用对象,因为最终生成的依赖注入代码中,返回的不是你定义的那个对象。那怎样可以不写这个,但又不会编译错误呢?答案是抛异常。

代码语言:javascript
复制
func injectFoo() Foo {
    panic(wire.Build(/* ... */))
}

最佳实践

Options Structs

如果一个对象依赖太对对象,Provider 的方法签名就很长。当然可以使用 Struct Provider,不过这样就不能做一些初始化操作(以前写在 Provider 里的)。封装参数可以解决这个问题。

代码语言:javascript
复制
type Options struct {
    // Messages is the set of recommended greetings.
    Messages []Message
    // Writer is the location to send greetings. nil goes to stdout.
    Writer io.Writer
}

func NewGreeter(ctx context.Context, opts *Options) (*Greeter, error) {
    // ...
}

var GreeterSet = wire.NewSet(wire.Struct(new(Options), "*"), NewGreeter)
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-3-25 1,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 介绍
  • 使用示例
    • 未使用依赖注入
      • 在本设计中,有三种结构类型:
      • 相应的初始化函数
      • 主函数
    • 使用 Wire
      • 主函数
      • 需要编写的 wire.go
      • 使用 wire 命令工具
      • 生成的依赖注入代码 wire_gen.go
    • 支持返回异常
      • 主函数
      • 依赖注入声明函数
      • 生成的依赖注入代码
    • 给依赖提供参数
      • 需要参数的依赖
      • 依赖注入声明函数
      • 生成的依赖注入代码
  • 进阶使用
    • Provider 分组
      • 子包下的 wire.go
      • 主包下的 wire.go
    • 接口绑定实现类
      • 接口
      • 实现类
      • 依赖接口
      • 依赖注入声明函数
    • Struct Providers
      • 注入所有字段
    • 提供初始值
      • 生成的依赖注入代码
      • 给接口提供初始值
    • 字段作为依赖
      • 使用 wire.FieldsOf()
      • 生成的依赖注入代码
    • 提供清理函数
      • 不 return 最终生成的对象
      • 最佳实践
        • Options Structs
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档