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 包的内容,这可能会导致导入循环的问题。

image

一段时间前,我通过将所有接口放在一个单独的包中来解决这个问题,但我发现那样做非常混乱。但我也真的不想将我的实现放在与接口相同的包中,因为我可能希望将实现拆分到不同的文件中。

现在我的问题是:你如何围绕这一点构建你的结构?


更多关于Golang中如何用接口和实现来组织包结构的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

哦,是的,我没想到Go语言在标准库中也是这样做的,正如你所说。非常感谢你的回复。smile

更多关于Golang中如何用接口和实现来组织包结构的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


或者还有其他方法,比如整洁架构,像这个使用了CQRS、DDD等等的示例:

github.com

wild-workouts-go-ddd-example/internal at master ·…

master/internal

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 包就是所有东西汇集的地方。它会知道 persistpersist/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
}

// 实现其他接口方法

这种结构确保了:

  1. 接口定义在独立的包中
  2. 实现包只导入接口包
  3. 使用方同时导入接口包和实现包
  4. 避免了循环导入问题
回到顶部