返回
创建于
状态公开

这段代码是 Go 中一种常见的**面向接口编程(interface-oriented programming)**的写法,配合“结构体实现接口”的方式,实现了解耦、封装、测试友好等目标。

我们来逐句讲解:


type Service interface { ... }

这是定义了一个接口类型 Service,它代表“数据库服务需要具备的能力”。这个接口声明了以下方法:

  • Health(ctx context.Context) error:健康检查(是否连通数据库)
  • Client() *ent.Client:返回 ent ORM 的原始客户端
  • GetAllKeeps(...) / GetAllMoments(...):查询数据库的两种实体
  • Close() error:关闭连接

注意:接口本身没有数据字段,只是规定了实现者必须有哪些方法。


type Database struct { client *ent.Client }

这个结构体 Database 是真正的数据库服务实现者。它内部嵌套了一个 ent ORM 客户端:

go
1client *ent.Client

也就是说:你实际与数据库交互时,会通过 ent.Client 来做,但封装在 Database 结构体中。


Database implements the Service interface

这一句是注释,不是语法,但很重要。意思是:

  • Database 实现了 Service 接口中定义的所有方法;
  • 所以你可以将 *Database 类型赋值给 Service 类型变量;
  • 这就是“接口与实现分离”的关键。

Go 的接口实现是隐式的 —— 不需要 implements 关键字,只要你定义了接口中的所有方法,Go 编译器就会认为你实现了它。


✅ 使用场景:面向接口编程

示例:

go
1func InitApp(db database.Service) {
2	// 参数是接口类型,使用 db.GetAllKeeps() 等方法
3}

你可以把任意实现了 Service 的对象传入进去,比如:

go
1db, _ := database.New()
2InitApp(db) // 传入的是 *Database,但接收者只知道是接口 Service

这样设计的好处是:

  • 将使用者与具体实现解耦;
  • 可以更方便地 mock 接口进行单元测试;
  • 可以未来轻松切换数据库实现(比如从 PostgreSQL 切到 MySQL)而不改业务逻辑。

✅ 图示理解:

js
1+-----------------+        +-------------------------+
2|   Service 接口   |<-------|     Database 结构体      |
3|                 |        | - client *ent.Client    |
4| + Health()      |        | + Health()              |
5| + GetAllKeeps() |        | + GetAllKeeps()         |
6| + Close()       |        | + Close()               |
7+-----------------+        +-------------------------+
89         |
10   程序中依赖这个接口,不关心底层 Database 的实现细节

这段代码就是在做:

定义了一个通用的“数据库服务接口”,然后用 Database 结构体来具体实现这个接口,达到解耦和便于测试的目的。

🧠 什么叫“方便替换数据库实现”?

就是说:你不直接在业务代码中依赖某个具体的数据库实现(如 PostgreSQL、MySQL、MongoDB 等),而是依赖一个统一的接口(interface)

这样的话:

  • 你可以随时更换底层数据库实现(只要实现了这个接口);
  • 上层业务代码完全不需要改变
  • 测试代码也可以用Mock 实现来替代真实数据库。

🧱 举个例子:统一接口

定义接口:

go
1type Service interface {
2	GetAllKeeps(ctx context.Context) ([]*ent.Keep, error)
3	Close() error
4}

PostgreSQL 实现:

go
1type PostgresDB struct { client *ent.Client }
2
3func (p *PostgresDB) GetAllKeeps(...) { /* PostgreSQL 查询 */ }
4func (p *PostgresDB) Close() error    { return p.client.Close() }

MongoDB 实现(假设你用 Mongo 驱动):

go
1type MongoDB struct { client *mongo.Client }
2
3func (m *MongoDB) GetAllKeeps(...) { /* Mongo 查询实现 */ }
4func (m *MongoDB) Close() error    { return m.client.Disconnect(...) }

✅ 业务代码只用接口,不管底层实现:

go
1func HandleGetKeeps(svc database.Service) {
2	keeps, _ := svc.GetAllKeeps(context.Background())
3	fmt.Println("获取到的 Keeps:", keeps)
4}

调用时随意切换:

go
1var svc database.Service
2
3if usePostgres {
4	svc = NewPostgresService()
5} else {
6	svc = NewMongoService()
7}
8
9HandleGetKeeps(svc)

🧪 测试时 Mock 实现:

go
1type MockDB struct{}
2
3func (m *MockDB) GetAllKeeps(ctx context.Context) ([]*ent.Keep, error) {
4	return []*ent.Keep{{ID: 1, Title: "测试用 Keep"}}, nil
5}
6func (m *MockDB) Close() error { return nil }
go
1func TestHandleGetKeeps(t *testing.T) {
2	mock := &MockDB{}
3	HandleGetKeeps(mock) // 不用连数据库,也能测试逻辑
4}

🚀 总结:为什么这样设计好?

好处说明
替换数据库更容易换 Mongo、MySQL、SQLite 只需实现同一接口
测试更方便不依赖真实数据库,用 mock 就能跑单元测试
业务逻辑更干净业务代码不用关心底层数据库细节,只和接口打交道
解耦更彻底数据库代码改了,其他模块不用改