笔者在使用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%的特殊功能,基本功能所需要的代码量是非常少的。
阅读更多相关文章,请关注知乎专栏【码洞】