golang常用库包:Go依赖注入(DI)工具-wire使用

产生的依赖注入库线:

什么是依赖注入

依赖注入,英文全称是DI的简称。

维基百科解释:

依赖注入是指程序运行时,如果需要调用另一个对象来辅助,不需要在代码中创建被调用者,而是依赖外部注入。

在用编程语言编写程序时,比如使用java语言,会编写很多类,这些类相互调用以完成特定的功能。

例如,要从 MySQL 获取数据,需要 MySQL 操作类。

第一次写MySQL操作类:

class MySQL{
}

要从mysql中获取数据,还需要mysql数据库的用户名、密码、地址等配置信息。继续写MySQL类:

package com.demo.mysql
class MySQL {
    getMySQLConfig() {
        port = 3306;
        username = "xxx";
        password = "xxx";
    }
    
    initMySQL(){}
   
    querySQL(){}
}

进一步思考,上面的MySQL操作程序有什么问题?

编程原则之一是: 单一职责

也就是说,一个班最好只做一件事。

按照这个原理看一下MySQL类,里面包含了获取数据库配置数据和操作MySQL的方法,不是一个职责。

数据库配置数据哪里来的,能不能拿出来用一个类来表示呢?当然。

因为MySQL的配置数据大多是从文件中读取的,所以上面的MySQL类是硬编码的,这也是一个不合理的地方。

配置文件的来源可以是yml格式文件、toml格式文件或远程文件。

第二次写mysql操作类:

修改上面的类,增加一个类来获取数据库配置:

package com.demo.mysql
class MySQLConfig {
      getMySQLConfig() {
        // 从配置文件获取 mysql 配置数据
    }
}

获取数据的类变成:

package com.demo.mysql
class MySQL {
    initMySQL(){
     // 获取数据库的配置信息
     mysqlconfig = new MySQLConfig();
    }
   
    querySQL(){}
}

图片[1]-golang常用库包:Go依赖注入(DI)工具-wire使用-唐朝资源网

想一想,上面重写的类有什么问题?

获取mysql的配置信息,需要在mysql类中new并实例化吗?如果不在同一个包下,必须先引入配置类,才能实例化。当然可以在这里优化。

直接将数据库配置类注入到 MySQL 操作类中。这是依赖注入。

完成数据操作,mysql操作类需要依赖数据库配置类,将数据库配置类注入mysql操作类,完成操作类功能。

第三次写mysql操作类:

伪代码示例:

package com.demo.mysql
class MySQL {
    private MySQLConfig config
    MySQL(MySQLConfig mysqlconfig) { // 数据库配置类这里注入到mysql操作类里
        config = mysqlconfig
    }
    initMySQL(){
    
    }
   
    querySQL(){}
}

将数据库配置类注入mysql操作类。

写过java的人都知道,java框架里有一个全家桶。框架包有两个核心,一个是IoC,一个是aop。

IoC的全称:控制反转。

这种控制反转也是面向对象编程的原则之一。

然而,这种控制反转很难理解。如果结合上面的DI来理解,会更容易理解。

DI 可以被认为是 IoC 编程原理的具体实现。

依赖注入也可以从其他软件设计思路来理解:

关注点分离高内聚、低耦合

对于数据库mysql的操作和mysql的配置信息,这两者可以独立,也可以分开。

何时使用依赖注入

当你的项目很小,文件不多,一个文件调用只需要传入少量的依赖对象,那么使用依赖注入会使程序变得繁琐。

当规模变大,单个对象需要调用多个依赖对象,而这些依赖又有自己的依赖对象时,对象的创建就变得很麻烦,这时依赖注入就可以发挥作用了。

线材概念描述线材介绍

Wire 是一个用 Go 语言实现的开源依赖注入代码生成工具。它可以根据你编写的代码生成相应的依赖注入 Go 代码。

不同于其他的依赖注入工具,比如uber的dig和uber,这两个工具是通过反射来实现依赖注入的,它们是运行时注入( )。

wire 是在编译代码时对生成代码的依赖注入,在编译时注入依赖代码(-time)。而且,在代码生成过程中,如果依赖注入出现问题,在生成依赖代码的时候就会报错,而无需等到代码运行暴露问题,就可以上报问题。

首先,你需要了解wire的2个核心概念:和。

从上面java模拟依赖注入的例子,可以简化依赖注入的步骤:

第一:需要新建一个类实例

二:通过构造函数或其他方法将New类实例“注入”到需要使用它的类中

第三:在类中使用这个New实例

从以上步骤可以了解wire 和 的两个核心概念。

它相当于上面 New 中的类实例。

相当于在“注入”动作之前聚合了需要的依赖函数,并根据聚合后的函数生成依赖。

: 提供一个对象。

:负责根据对象依赖生成新程序。

是一个普通的Go函数,可以理解为对象的构造函数。为以下生成器函数提供“工件”。

请参阅下面的示例,来自 go 博客。

本博发表于2018.10.9,有些资料可能有点老,请参考指南,本指南最后更新于2021.1. 26.

下面的 () 函数可以看作是一个。这个函数需要传入*和*mysql.DB 2个参数。

// NewUserStore 是一个 provider for *UserStore,*UserStore 依赖 *Config,*mysql.DB

图片[2]-golang常用库包:Go依赖注入(DI)工具-wire使用-唐朝资源网

func NewUserStore(cfg *Config, db *mysql.DB) (*UserStore, error) {... ...} // NewDefaultConfig 是一个 provider for *Config,没有任何依赖 func NewDefaultConfig() *Config {...} // NewDB 是 *mysql.DB 的一个 provider ,依赖于数据库连接信息 *ConnectionInfo func NewDB(info *ConnectionInfo) (*mysql.DB, error){...}

可以组合成一组。这对于经常一起使用非常有用。使用电线。组合它们的方法,

var SuperSet = wire.NewSet(NewUserStore, NewDefaultConfig)

您还可以将其他集合添加到集合中,

import (
    “example.com/some/other/pkg”
)
// ... ...
var MegaSet = wire.NewSet(SuperSet, pkg.OtherSet)

线。()功能:

此功能可用于将相关项目组合在一起。当然也可以单独使用,比如var = wire.(NewDB)。

这个函数的返回值也可以作为其他函数的参数,比如上面作为参数。

我们写一个程序把这些组合起来(比如下面的()函数),wire中的wire命令会按照依赖的顺序被调用,生成一个比较完整的函数,就是这样。

首先编写生成的签名函数,然后使用wire命令生成对应的函数。

示例如下:

// +build wireinject
func initUserStore(info *ConnectionInfo) (*UserStore, error) {
    wire.Build(SuperSet, NewDB) // 声明获取 UserStore 需要调用哪些 provider 函数
    return nil, nil
}

然后使用wire命令从上面的函数生成一个函数,生成的函数对应文件名.go。

电线命令:

您可以通过 Wire 中的 .

直接在生成函数的包下,使用wire命令生成代码。

wire.Build() 函数:

它的参数可以是一个或多个wire.() 组,也可以直接使用它们。

线使用线结构和方法列表

func Build(...interface{}) string
type Binding
	func Bind(iface, to interface{}) Binding
type ProvidedValue
	func InterfaceValue(typ interface{}, x interface{}) ProvidedValue
	func Value(interface{}) ProvidedValue
type ProviderSet
	func NewSet(...interface{}) ProviderSet
type StructFields
	func FieldsOf(structType interface{}, fieldNames ...string) StructFields
type StructProvider
	func Struct(structType interface{}, fieldNames ...string) StructProvider

更多详细信息可以在这里找到。

电线安装

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

快速入门示例 1

先新建一个文件夹,然后在里面使用go mod init,新建一个go.mod,在go.mod中引入wire://wire v0.5.0。

整个文件夹目录结构:

定义

在文件夹中新建一个 .go 文件,编写如下代码:

package main
import (
	"context"
	"errors"
)
type Student struct {
	ClassNo int
}
// NewStudent 就是一个 provider,返回一个 Student
func NewStudent() Student {
	return Student{ClassNo: 10}
}
type Class struct {
	ClassNo int
}
// NewClass 就是一个 provider,返回一个 Class
func NewClass(stu Student) Class {
	return Class{ClassNo: stu.ClassNo}
}
type School struct {
	ClassNo int
}
// NewSchool 是一个 provider,返回一个 School
// 与上面 provider 不同的是,它还返回了一个错误信息
func NewSchool(ctx context.Context, class Class) (School, error) {
	if class.ClassNo == 0 {
		return School{}, errors.New("cannot provider school when class is 0")
	}
	return School{ClassNo: class.ClassNo}, nil
}

定义

使用以下代码创建一个新文件wire.go:

// +构建

这行代码必须在包的最上面声明,说明这是一个可以编译的

使用连线命令生成功能代码

使用wire命令生成代码,执行目录下的wire命令:

图片[3]-golang常用库包:Go依赖注入(DI)工具-wire使用-唐朝资源网

$ wire
wire: D:workmygogo-practice2diwirebasicswire.go:9:1: inject initSchool: no provider found for context.Context needed by basics.School in provider set "SuperSet" (D:workmygogo-practice2diwirebasicswire.go:7:16)
wire: basics: generate failed
wire: at least one generate failure

报错,看显示的错误信息,最重要的是这行信息:

inject initSchool: no provider found for context.Context needed by basics.School in provider set "SuperSet"

看看功能,果然没有提供。. 让我们修改函数,导入包,给函数添加参数:

func initSchool(ctx context.Context) (School, error) {
	wire.Build(SuperSet)
	return School{}, nil
}

然后使用命令行编译:

$ wire
wire: basics: wrote D:workmygogo-practice2diwirebasicswire_gen.go

生成的代码,.go 文件,

// Code generated by Wire. DO NOT EDIT.
//go:generate go run github.com/google/wire/cmd/wire
//+build !wireinject
package main
import (
	"context"
	"github.com/google/wire"
)
// Injectors from wire.go:
func initSchool(ctx context.Context) (School, error) {
	student := NewStudent()
	class := NewClass(student)
	school, err := NewSchool(ctx, class)
	if err != nil {
		return School{}, err
	}
	return school, nil
}
// wire.go:
var SuperSet = wire.NewSet(NewStudent, NewClass, NewSchool)

概括

电线使用步骤:

先写吧。重写:将相关的组合为一个。最后用wire命令编译:wire会根据它们之间的依赖关系生成代码。

金属丝。功能:

它可以放在一起。功能1分类:可以将一组相关的项目写在一起。行动 1 扩展了行动 2,避免了太多难以管理的事情。

图片[4]-golang常用库包:Go依赖注入(DI)工具-wire使用-唐朝资源网

wite.Build 函数:

func Build(...interface{}) string

它的参数是一个不定长度的列表。将所有相关的组合在一起并生成功能代码。它是一个生成函数的模板函数。

绑定接口

上面的示例 1 绑定了结构和构造函数。如果涉及到接口怎么办?例如下面的代码,

type Fooer interface {
    Hello()
}
type Foo struct{}
func (f Foo)Hello() {
    fmt.Println("hello")
}
func Bar struct{}
func NewBar() Bar {
    return Bar{}
}

有个接口Fooer,这个怎么绑定?这时候可以使用[wire.Bind]()函数,

var bind = wire.Bind(new(Fooer), new(Foo))
var set = wire.NewSet(bind, NewBar)
// or
var set = wire.NewSet(wire.Bind(new(Fooer), new(Foo)), NewBar)

它也可以直接作为一个使用。如果结构仅用于字段分配,则可以使用功能线。赋值。

type Foo int
type Bar int
func NewFoo() Foo {/* ... */}
func NewBar() Bar {/* ... */}
type FooBar struct {
    MyFoo Foo
    MyBar Bar
}
var set = wire.NewSet(
	NewFoo,
    NewBar,
    wire.Struct(new(FooBar), "MyFoo", "MyBar"),
)

欲了解更多信息,请参阅

在上面的示例 1 中,使用了 set,并且

相关组在一起。使用功能线。去做吧。

更多示例请查看官方文档:

参考

© 版权声明
THE END
喜欢就支持一下吧
点赞15赞赏 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容