你可以找到一个完整的代码示例在。例子包含了一个暴露 REST 接口的 http 服务器。

简介

在这篇博文我们介绍一种 Go 中依赖注入的方式 -- 使用更高阶的函数和闭包。

考虑下以下返回用户资料的 domain 层函数。

func GetUserProfile(id string) UserProfile {
    rows, err := db.Query("SELECT ...")
    ...
    return profileText
}

我们想要将操作用户数据和接入数据库的代码分离开。在这个例子中,我们想要对 domain 层和任意的业务逻辑进行单元测试,同时为数据库接入函数提供 mock。让我们把这些关系分离,使得每个函数拥有单一职责。

// 包含任意业务逻辑或者映射代码的 domain 层函数
func GetUserProfile(id string) User {
    ...
}

// 数据库接入层函数
func SelectUserByID(id string) UserProfile {
    ...
}
SelectUserByIDSelectUserByIDGetUserProfileGetUserProfilego

类型别名

GetUserProfilego
type SelectUserByID func(id string) User

type GetUserProfile func(id string) UserProfile

func NewGetUserProfile(selectUser SelectUserByID) GetUserProfile {
    return func(id string) string {
        user := selectUser(id)
        return user.ProfileText
    }
}

func selectUser(id string) User {
    ...
    return User{ProfileText: userRow.ProfileText}
}
SelectUserByIDNewGetUserProfileselectUser关闭关闭

我们可以像这样调用 domain 函数。

// 应用中某一处的连接依赖项
getUser := NewGetUserProfile(selectUser)

user := getUser("1234")

另一种看法

如果你对类似于 Java 这类的语言比较熟悉的话,这类似于创建了一个类,注入类依赖到构造器,然后在某个方法中访问依赖。其实和这个途径并没有功能性的区别,你可以认为函数的类型别名是一个简单抽象方法 (SAM) 的接口。在 Java 里我们可能会使用构造器注入依赖。

interface DB {
    User SelectUser(String id)
}

public class UserService {
    private final DB db;

    public UserService(DB db) { // 注入依赖到构造器中
        this.DB = db;
    }

    public UserProfile getUserProfile(String id) { // 访问 ( 依赖 ) 的方法
        User user = this.DB.SelectUser(id);
        ...
        return userProfile;
    }
}
go
type SelectUser func(id string) User

type GetUserProfile func(id string) UserProfile

func NewGetUserProfile(selectUser SelectUser) { // 注入依赖的工厂方法
    return func(id string) UserProfile { // 访问 ( 依赖 ) 的方法
        user := selectUser(id)
        ...
        return userProfile
    }
}

测试

现在可以对我们的 domain 层功能进行单元测试以及为数据库接入层提供 mock。

func TestGetUserProfile(t *testing.T) {
    selectUserMock := func(id string) User {
        return User{name: "jan"}
    }
    getUser := NewGetUserProfile(selectUserMock)

    user := getUser("12345")

    assert.Equal(t, UserProfile{ID: "12345", Name: "jan"}, user)
}

你可以找到一个完整的代码示例在。例子包含了一个暴露 REST 接口的 http 服务器。


本文由 原创编译, 荣誉推出