Golang中如何用接口和实现来组织包结构
Golang中如何用接口和实现来组织包结构
当你有一个包含 Database 接口的 database 包,并且在该包内还有针对不同类型数据库实现 Database 接口的子包时,你如何处理以避免循环导入。
为了更清晰地说明,这里有一个我代码中的具体示例。这是我的 Database 接口。
type Database interface {
DatabaseBinding
Close() error
Name() string
Migrate(args ...string) error
BeginTransaction() (TransactionWrapper, error)
}
如你所见,还有另一个接口 DatabaseBinding,它包含了特定数据库查询的方法,这些方法在此场景下无关紧要。它还包含一个方法 BeginTransaction,该方法返回一个同样位于 database 包中的 TransactionWrapper 接口。
现在,当我在一个名为 postgres 的包中进行实现时,我需要在其中引用 database 包。如果我想在另一个包中同时使用 postgres 包和 database 包的内容,这可能会导致导入循环的问题。

一段时间前,我通过将所有接口放在一个单独的包中来解决这个问题,但我发现那样做非常混乱。但我也真的不想将我的实现放在与接口相同的包中,因为我可能希望将实现拆分到不同的文件中。
现在我的问题是:你如何围绕这一点构建你的结构?
更多关于Golang中如何用接口和实现来组织包结构的实战教程也可以访问 https://www.itying.com/category-94-b0.html
哦,是的,我没想到Go语言在标准库中也是这样做的,正如你所说。非常感谢你的回复。
更多关于Golang中如何用接口和实现来组织包结构的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
或者还有其他方法,比如整洁架构,像这个使用了CQRS、DDD等等的示例:
wild-workouts-go-ddd-example/internal at master ·…
Go DDD 示例应用程序。完整的项目,通过实际重构展示如何应用 DDD、整洁架构和 CQRS。 - wild-workouts-go-ddd-example/internal at master · ThreeDotsLabs/wild-…
嗨,@zekro,我的数据库接口也采用了相同的结构:通常我会有一个 persist 包来定义仓库接口,然后为具体的实现创建子包。我避免循环依赖的方式是让嵌套更深的包依赖于嵌套较浅的包。例如,我认为 Go 中的 net/http 包导入更通用的 net 包是合理的。os/exec 很可能“知道” os。所以我的 persist/sqlpersist 包会从 persist 导入,但 persist 不能从 persist/sqlpersist 导入。
然后我的 main 包就是所有东西汇集的地方。它会知道 persist 和 persist/sqlpersist,并且能够使用在 persist 中定义的接口的 SQL 实现。
我在 Stack Overflow 上找到了一个答案,它说了同样的事情并且更详细:go - Cyclic dependencies and interfaces - Stack Overflow
一种常见的做法是将接口定义放在独立的包中,避免实现包与接口包之间的循环依赖。以下是具体实现示例:
// database/database.go
package database
type Database interface {
Close() error
Name() string
Migrate(args ...string) error
BeginTransaction() (TransactionWrapper, error)
}
type TransactionWrapper interface {
Commit() error
Rollback() error
}
// database/postgres/postgres.go
package postgres
import "yourproject/database"
type PostgresDB struct {
// 实现细节
}
func (p *PostgresDB) Close() error {
// 具体实现
return nil
}
func (p *PostgresDB) Name() string {
return "postgres"
}
func (p *PostgresDB) Migrate(args ...string) error {
// 具体实现
return nil
}
func (p *PostgresDB) BeginTransaction() (database.TransactionWrapper, error) {
// 返回PostgresTransaction实现
return &PostgresTransaction{}, nil
}
type PostgresTransaction struct{}
func (pt *PostgresTransaction) Commit() error {
return nil
}
func (pt *PostgresTransaction) Rollback() error {
return nil
}
// main.go
package main
import (
"yourproject/database"
"yourproject/database/postgres"
)
func main() {
var db database.Database
db = &postgres.PostgresDB{}
// 使用接口方法
_ = db.Close()
}
另一种模式是使用内部包来避免导出实现细节:
// database/database.go
package database
import "yourproject/database/internal/postgres"
func NewPostgres() Database {
return postgres.New()
}
// database/internal/postgres/postgres.go
package postgres
type postgresDB struct {
// 非导出类型
}
func New() *postgresDB {
return &postgresDB{}
}
func (p *postgresDB) Close() error {
return nil
}
// 实现其他接口方法
这种结构确保了:
- 接口定义在独立的包中
- 实现包只导入接口包
- 使用方同时导入接口包和实现包
- 避免了循环导入问题

