长安链源码学习v2.2.1--ioc机制(九)
长安链技术不断迭代,距离前面的教程已经过去一年多,迭代非常多的功能与特性,本次基于2.2.1版本继续学习长安链源码,相比早期教程,给阅读带来一定复杂度的是IOC,我们先来分析它。
1. 介绍
关于ioc的含义,网上的介绍有很多,这里不多赘述。简单来说这是一种降低模块间耦合的方式,在运行期间动态关联对象的某种实现。
ioc机制在长安链store 存储模块中使用较多,例如:根据配置加载区块存储引擎,文件形式、KV形式、SQL形式等。IOC代码位置在chainmaker.org/chainmaker/common/v2/container
。
2. 使用方法
2.1 简单使用1
通过一个例子来学习,首先找一个干净的工程:
1)定义区块存储接口
type BlockStore interface {
Set(key, vaule string)
Get(key string) string
}
2)文件存储实现上述接口,注意NewFileStore
方法的返回值是interface
type FileStore struct {
}
//注意该方法的返回值是interface
func NewFileStore() BlockStore{
return &FileStore{}
}
func (*FileStore) Set(key, vaule string) {
}
func (*FileStore) Get(key string) string {
return "filestore-get"
}
3)通过IOC实现依赖注入,通过Register
方法将生成文件存储对象的方法注册到IOC
中心,通过Resolve
将bs
接口说对象与FileStore
实现对象关联。(如果chainmaker.org/chainmaker/common/v2/container
找不到,可用过go mod tidy
拉取。)
import (
"fmt"
"chainmaker.org/chainmaker/common/v2/container"
)
func main() {
c := container.NewContainer()
err := c.Register(NewFileStore)
if err != nil {
panic(err)
}
var bs BlockStore
err = c.Resolve(&bs)
if err != nil {
panic(err)
}
fmt.Println(bs.Get("123"))
}
2.2 简单使用2
1)定义区块存储接口(这步骤不变)
type BlockStore interface {
Set(key, vaule string)
Get(key string) string
}
2)文件存储实现上述接口,注意NewFileStore
方法的返回值是struct
type FileStore struct {
}
//注意该方法的返回值是数据结构
func NewFileStore() *FileStore{
return &FileStore{}
}
func (*FileStore) Set(key, vaule string) {
}
func (*FileStore) Get(key string) string {
return "filestore-get"
}
3)通过IOC实现依赖注入,这里使用container.Interface
,含义是不理会NewFileStore
返回值类型,根据container.Interface
中的类型绑定NewFileStore
实现。
import (
"chainmaker.org/chainmaker/common/v2/container"
"fmt"
)
func main() {
c := container.NewContainer()
var bsi BlockStore
err := c.Register(NewFileStore, container.Interface(&bsi))
if err != nil {
panic(err)
}
var bs BlockStore
err = c.Resolve(&bs)
if err != nil {
panic(err)
}
fmt.Println(bs.Get("123"))
}
2.3 同一接口多种实现
增加一种BlockStore实现方式:KV数据库实现
type KVStore struct {
}
//注意该方法的返回值是数据结构
func NewKVStore() *KVStore {
return &KVStore {}
}
func (*KVStore ) Set(key, vaule string) {
}
func (*KVStore ) Get(key string) string {
return "kvstore-get"
}
默认绑定第一个实现对象(NewFileStore
),配置container.Default()
优先绑定,下面的案例绑定NewKVStore
。
import (
"chainmaker.org/chainmaker/common/v2/container"
"fmt"
)
func main() {
c := container.NewContainer()
var bsi BlockStore
err := c.Register(NewFileStore, container.Interface(&bsi))
if err != nil {
panic(err)
}
err = c.Register(NewKVStore, container.Interface(&bsi), container.Default())
if err != nil {
panic(err)
}
var bs BlockStore
err = c.Resolve(&bs)
if err != nil {
panic(err)
}
fmt.Println(bs.Get("123"))
}
2.3 构造方法带参数
把KVStore删除,本Case模拟不需要。构造方法增加一个flag并打印flag的值。
type FileStore struct {
}
func NewFileStore(flag string) *FileStore{
fmt.Println(flag)
return &FileStore{}
}
func (*FileStore) Set(key, vaule string) {
}
func (*FileStore) Get(key string) string {
return "filestore-get"
}
在Register期间补充默认参数container.Parameters
, 在Resolve
绑定期间可以替换该参数container.Arguments
,下面例子输出为Arguments
。
import (
"fmt"
"chainmaker.org/chainmaker/common/v2/container"
)
func main() {
c := container.NewContainer()
var bsi BlockStore
err := c.Register(NewFileStore, container.Interface(&bsi), container.Parameters(map[int]interface{}{0: "Parameters"}))
if err != nil {
panic(err)
}
var bs BlockStore
err = c.Resolve(&bs, container.Arguments(map[int]interface{}{0: "Arguments"}))
if err != nil {
panic(err)
}
fmt.Println(bs.Get("123"))
}
2.4 其他
上面几种用法是比较常见长安链中的使用方法,还有其他用法:
1)除了上述Default
方式外,可在Resolve
期间使用ResolveName
指定绑定实现的名称,该名称在Register
期间设置。
2)IOC单例与多例的设置Lifestyle
3)支持构造方法参数忽略Optional
4)构造方法的参数需要依赖其他IOC对象,依靠DependsOn
绑定实现
思考:
在学习长安链IOC机制的时候,一直在思考使用IOC的必要性,关于这点并没有在官方资料上找到,相信官方团队有自己的考量,后续将持续关注官方文档资料,将设计思想补充到本节。