依赖注入不是Java的专利,Golang也有

笔者在使用Golang的时候就发现构建系统依赖树非常繁琐,New了很多对象,又手工代码将它们拼接起来,写了一堆非常冗繁的代码。然后就开始想,要是Golang像Java一样有一个好用的依赖注入框架就好啦。

果不其然,Golang还真有,居然还是大厂facebook团队开源的。


Golang的很多用户都不是来自Java,依赖注入他们可能听过,可是从来没有玩过。为了说明依赖注入有多好用,我先用Java代码来解释一下。

先来看一下没有依赖注入的Java世界是怎样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
复制代码class DBEngine {
}

class CacheEngine {

}

class UserDB {

private DBEngine db;
private CacheEngine cache;

public UserDB(DBEngine db, CacheEngine cache) {
this.db = db;
this.cache = cache;
}

}

class ItemDB {

private DBEngine db;
private CacheEngine cache;

public ItemDB(DBEngine db, CacheEngine cache) {
this.db = db;
this.cache = cache;
}

}

class UserService {

private UserDB db;

public UserService(UserDB db) {
this.db = db;
}

}

class ItemService {

private ItemDB db;

public ItemService(ItemDB db) {
this.db = db;
}

}

class App {

private UserService user;
private ItemService item;

public App(UserService user, ItemService item) {
this.user = user;
this.item = item;
}
}

public class GuiceTest {

public static void main(String[] args) {
DBEngine db = new DBEngine();
CacheEngine cache = new CacheEngine();
UserDB userDB = new UserDB(db, cache);
ItemDB itemDB = new ItemDB(db, cache);
UserService userServ = new UserService(userDB);
ItemService itemServ = new ItemService(itemDB);
App app = new App(userServ, itemServ);
}

}

在main方法里面,我们new出来很多对象,然后用他们构造了一颗依赖树。我们还写了很多构造器,为了能方便的构造出每个节点。


然后我们用依赖注入框架来改造它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
复制代码class DBEngine {
}

class CacheEngine {
}

@Singleton
class UserDB {

@Inject
private DBEngine db;
@Inject
private CacheEngine cache;

}

@Singleton
class ItemDB {
@Inject
private DBEngine db;
@Inject
private CacheEngine cache;

}

@Singleton
class UserService {

@Inject
private UserDB db;

}

@Singleton
class ItemService {

@Inject
private ItemDB db;

}

@Singleton
class App {

@Inject
private UserService user;
@Inject
private ItemService item;

}

class AppModule extends AbstractModule {

@Override
protected void configure() {
DBEngine db = new DBEngine();
CacheEngine cache = new CacheEngine();
bind(DBEngine.class).toInstance(db);
bind(CacheEngine.class).toInstance(cache);
}

}

public class GuiceTest {

public static void main(String[] args) {
Injector injector = Guice.createInjector(new AppModule());
App app = injector.getInstance(App.class);
}

}

这里我们使用的是另一个开源大厂google的依赖注入框架Guice。我们发现main方法缩减了很多代码,所有的new操作都不见了,然后我们还发现每个对象的构造器也消失了,取而代之的是多了两个注解@Singleton和@Inject,Singleton表示类是单例的,Inject表示当前字段使用依赖注入自动设置。好处不用多说,一目了然,就是帮我们节省代码,省去了很多系统初始化时构建一系列对象的细节。另外Guice还需要定义一个Module,把依赖树的叶子节点手工实例化一下,叶子结点对象往往不是简单的依赖注入,而需要手动构造。如果把整个系统的状态比喻成现实的物理世界,那么Module里面干的事就是宇宙大爆炸,所有一切对象的输入源点。依赖注入仅仅帮我们省去了中间节点的构建工作。

在我们的例子中,这棵树还谈不上复杂,毕竟节点数很有限,节点之间的连接也很有限。在大型的复杂业务系统中,这样的对象那就是成百上千了,如果没有使用依赖注入的话,那就真是剪不断理还乱了。

好,接下来我们说说facebookgo团队开源的这个Inject框架如何使用。我们还使用上面的例子,用golang 改写一下。

首先,我们看一下没有依赖注入的Golang世界是怎样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
复制代码type DBEngine struct{}

func NewDBEngine() *DBEngine {
return &DBEngine{}
}

type CacheEngine struct{}

func NewCacheEngine() *CacheEngine {
return &CacheEngine{}
}

type UserDB struct {
db *DBEngine
cache *CacheEngine
}

func NewUserDB(db *DBEngine, cache *CacheEngine) *UserDB {
return &UserDB{db, cache}
}

type ItemDB struct {
db *DBEngine
cache *CacheEngine
}

func NewItemDB(db *DBEngine, cache *CacheEngine) *ItemDB {
return &ItemDB{db, cache}
}

type UserService struct {
db *UserDB
}

func NewUserService(db *UserDB) *UserService {
return &UserService{db}
}

type ItemService struct {
db *ItemDB
}

func NewItemService(db *ItemDB) *ItemService {
return &ItemService{db}
}

type App struct {
user *UserService
item *ItemService
}

func NewApp(user *UserService, item *ItemService) *App {
return &App{user, item}
}

func main() {
db := NewDBEngine()
cache := NewCacheEngine()
userDB := NewUserDB(db, cache)
itemDB := NewItemDB(db, cache)
userServ := NewUserService(userDB)
itemServ := NewItemService(itemDB)
_ = NewApp(userServ, itemServ)
}

跟Java版本一样,定义了不少构造器,然后手工组装依赖树。

然后我们把这段代码改造成facebookgo依赖注入版本的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
复制代码import "github.com/facebookgo/inject"

type DBEngine struct{}

func NewDBEngine() *DBEngine {
return &DBEngine{}
}

type CacheEngine struct{}

func NewCacheEngine() *CacheEngine {
return &CacheEngine{}
}

type UserDB struct {
db *DBEngine `inject:""`
cache *CacheEngine `inject:""`
}

type ItemDB struct {
db *DBEngine `inject:""`
cache *CacheEngine `inject:""`
}

type UserService struct {
db *UserDB `inject:""`
}

type ItemService struct {
db *ItemDB `inject:""`
}

type App struct {
user *UserService `inject:""`
item *ItemService `inject:""`
}

func main() {
db := NewDBEngine()
cache := NewCacheEngine()
var g inject.Graph
var app App
g.Provide(
&inject.Object{Value: &app},
&inject.Object{Value: db},
&inject.Object{Value: cache})
g.Populate()
// use app do something
}

这个跟Java版本也很类似,只是Module的定义直接放在了main方法里,也就是上面代码中的Provide方法调用,@Singleton不需要了,@Inject变成了inject:""。然后用Populate方法一次性将整个依赖树构建出来就可以使用了。

看这个开源库的源码发现,整个类库的实现才500多行代码。这是多么轻量级的一个类库,只不过代码这么短,功能也不会太多,相比Java的依赖注入而言,它的功能就单一太多了。不过没关系,相比Guice而言这些缺失的功能不是必须的,能帮我们省掉很多代码它已经做得很好了,这就足够了。要知道Java里面非常完善的依赖注入框架80%的代码那都是为了实现那20%的特殊功能,基本功能所需要的代码量是非常少的。

阅读更多相关文章,请关注知乎专栏【码洞】

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%