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

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

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

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

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

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出来很多对象,然后用他们构造了一颗依赖树。我们还写了很多构造器,为了能方便的构造出每个节点。

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

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世界是怎样的。

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依赖注入版本的

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%的特殊功能,基本功能所需要的代码量是非常少的。

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