返回
创建于
状态公开
这段代码是 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 客户端:
1client *ent.Client也就是说:你实际与数据库交互时,会通过 ent.Client 来做,但封装在 Database 结构体中。
✅ Database implements the Service interface
这一句是注释,不是语法,但很重要。意思是:
Database实现了Service接口中定义的所有方法;- 所以你可以将
*Database类型赋值给Service类型变量; - 这就是“接口与实现分离”的关键。
Go 的接口实现是隐式的 —— 不需要 implements 关键字,只要你定义了接口中的所有方法,Go 编译器就会认为你实现了它。
✅ 使用场景:面向接口编程
示例:
1func InitApp(db database.Service) {
2 // 参数是接口类型,使用 db.GetAllKeeps() 等方法
3}你可以把任意实现了 Service 的对象传入进去,比如:
1db, _ := database.New()
2InitApp(db) // 传入的是 *Database,但接收者只知道是接口 Service这样设计的好处是:
- 将使用者与具体实现解耦;
- 可以更方便地 mock 接口进行单元测试;
- 可以未来轻松切换数据库实现(比如从 PostgreSQL 切到 MySQL)而不改业务逻辑。
✅ 图示理解:
1+-----------------+ +-------------------------+
2| Service 接口 |<-------| Database 结构体 |
3| | | - client *ent.Client |
4| + Health() | | + Health() |
5| + GetAllKeeps() | | + GetAllKeeps() |
6| + Close() | | + Close() |
7+-----------------+ +-------------------------+
8 ▲
9 |
10 程序中依赖这个接口,不关心底层 Database 的实现细节这段代码就是在做:
定义了一个通用的“数据库服务接口”,然后用
Database结构体来具体实现这个接口,达到解耦和便于测试的目的。
🧠 什么叫“方便替换数据库实现”?
就是说:你不直接在业务代码中依赖某个具体的数据库实现(如 PostgreSQL、MySQL、MongoDB 等),而是依赖一个统一的接口(interface)。
这样的话:
- 你可以随时更换底层数据库实现(只要实现了这个接口);
- 上层业务代码完全不需要改变;
- 测试代码也可以用Mock 实现来替代真实数据库。
🧱 举个例子:统一接口
定义接口:
1type Service interface {
2 GetAllKeeps(ctx context.Context) ([]*ent.Keep, error)
3 Close() error
4}PostgreSQL 实现:
1type PostgresDB struct { client *ent.Client }
2
3func (p *PostgresDB) GetAllKeeps(...) { /* PostgreSQL 查询 */ }
4func (p *PostgresDB) Close() error { return p.client.Close() }MongoDB 实现(假设你用 Mongo 驱动):
1type MongoDB struct { client *mongo.Client }
2
3func (m *MongoDB) GetAllKeeps(...) { /* Mongo 查询实现 */ }
4func (m *MongoDB) Close() error { return m.client.Disconnect(...) }✅ 业务代码只用接口,不管底层实现:
1func HandleGetKeeps(svc database.Service) {
2 keeps, _ := svc.GetAllKeeps(context.Background())
3 fmt.Println("获取到的 Keeps:", keeps)
4}调用时随意切换:
1var svc database.Service
2
3if usePostgres {
4 svc = NewPostgresService()
5} else {
6 svc = NewMongoService()
7}
8
9HandleGetKeeps(svc)🧪 测试时 Mock 实现:
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 }1func TestHandleGetKeeps(t *testing.T) {
2 mock := &MockDB{}
3 HandleGetKeeps(mock) // 不用连数据库,也能测试逻辑
4}🚀 总结:为什么这样设计好?
| 好处 | 说明 |
|---|---|
| 替换数据库更容易 | 换 Mongo、MySQL、SQLite 只需实现同一接口 |
| 测试更方便 | 不依赖真实数据库,用 mock 就能跑单元测试 |
| 业务逻辑更干净 | 业务代码不用关心底层数据库细节,只和接口打交道 |
| 解耦更彻底 | 数据库代码改了,其他模块不用改 |