在线商城,需要在Go后台提供存储与查询产品的服务,既实现一个负责保存和检索产品的存储库。
productrepo/
└── api.go
0 directories, 1 file创建一个productrepo包和一个api.go文件,该API应该暴露出存储库里所有的产品方法。
// api.go
package productrepo
type ProductRepository interface {
StoreProduct(name string, id int)
FindProductByID(id int)
}在productrepo包下,定义了ProductRepository接口,它代表的就是存储库。该接口中定义两个方法:
StoreProduct()方法用于存储产品信息FindProductByID()方法通过产品ID查找产品信息存储库接口定义完成后,就需要有实体对象去实现该接口。
productrepo/
├── api.go
└── mock.go
0 directories, 2 files在productrepo包下,新建mock.go文件,定义mockProductRepo对象。
// mock.go
package productrepo
import "fmt"
// 实现接口的空实体对象
type mockProductRepo struct {}
func (m mockProductRepo) StoreProduct(name string, id int) {
fmt.Println("mocking the StoreProduct func")
}
func (m mockProductRepo) FindProductByID(id int) {
fmt.Println("mocking the FindProductByID func")
}在示例代码中mock出ProductRepository接口所需的方法。
然后在api.go中增加New()方法,它返回的一个实现了ProductRepository接口的对象。
// api.go
func New() ProductRepository {
return mockProductRepo{}
}对于已经定义的ProductRepository接口,可以有多种对象去实现它。
不同的数据库存储都需要实现ProductRepository接口定义的StoreProduct()方法和FindProductByID()方法。
以本地MySQL存储库为例,它要管理产品对象,需要实现ProductRepository接口。
productrepo/
├── api.go
├── mock.go
└── mysql.go
0 directories, 3 files在productrepo包下,新建mysql.go文件,定义了mysqlProductRepo对象并实现接口方法。
// mysql.go
package productrepo
import "fmt"
type mysqlProductRepo struct {
}
func (m mysqlProductRepo) StoreProduct(name string, id int) {
fmt.Println("mysqlProductRepo: mocking the StoreProduct func")
// In a real world project you would query a MySQL database here.
}
func (m mysqlProductRepo) FindProductByID(id int) {
fmt.Println("mysqlProductRepo: mocking the FindProductByID func")
// In a real world project you would query a MySQL database here.
}相似地,当项目中同时需要把产品信息存储到云端时,以阿里云为例。
productrepo/
├── aliyun.go
├── api.go
├── mock.go
└── mysql.go
0 directories, 4 files在productrepo包下,新建aliyun.go文件,定义了aliCloudProductRepo对象并实现接口方法。
// aliyun.go
package productrepo
import "fmt"
type aliCloudProductRepo struct {
}
func (m aliCloudProductRepo) StoreProduct(name string, id int) {
fmt.Println("aliCloudProductRepo: mocking the StoreProduct func")
// In a real world project you would query an ali Cloud database here.
}
func (m aliCloudProductRepo) FindProductByID(id int) {
fmt.Println("aliCloudProductRepo: mocking the FindProductByID func")
// In a real world project you would query an ali Cloud database here.
}此时,更新api.go中定义的New()方法。
// api.go
func New(environment string) ProductRepository {
switch environment {
case "aliCloud":
return aliCloudProductRepo{}
case "local-mysql":
return mysqlProductRepo{}
}
return mockProductRepo{}
}通过将环境变量environment传递给New()函数,它将基于该环境值返回ProductRepository接口的正确实现对象。
.
├── go.mod
├── main.go
└── productrepo
├── aliyun.go
├── api.go
├── mock.go
└── mysql.go
1 directory, 6 files定义程序入口main.go文件以及main函数。
// main.go
package main
import "productrepo"
func main() {
env := "aliCloud"
repo := productrepo.New(env)
repo.StoreProduct("HuaWei mate 40", 105)
}通过使用productrepo.New()方法基于环境值来获取ProductRepository接口对象。
如果需要切换产品存储库,则只需要使用对应的env值调用productrepo.New()方法即可。
// mysql.go
func NewMysqlProductRepo() *mysqlProductRepo {
return &mysqlProductRepo{}
}
//aliyun.go
func NewAliCloudProductRepo() *aliCloudProductRepo{
return &aliCloudProductRepo{}
}
// mock.go
func NewMockProductRepo() *mockProductRepo {
return &mockProductRepo{}
}// main.go
package main
import "productrepo"
func main() {
env := "aliCloud"
switch env {
case "aliCloud":
repo := productrepo.NewAliCloudProductRepo()
repo.StoreProduct("HuaWei mate 40", 105)
// the more function to do, the more code is repeated.
case "local-mysql":
repo := productrepo.NewMysqlProductRepo()
repo.StoreProduct("HuaWei mate 40", 105)
// the more function to do, the more code is repeated.
default:
repo := productrepo.NewMockProductRepo()
repo.StoreProduct("HuaWei mate 40", 105)
// the more function to do, the more code is repeated.
}
}在项目演进过程中,会迭代很多存储库对象,而通过ProductRepository接口,可以轻松地实现扩展,而不必反复编写相同逻辑的代码。
开发中,常常提到要功能模块化,上面示例:通过接口为载体,一类服务就是一个接口,实现接口即服务。