打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
使用 Go 编写干净的代码:接受接口并返回结构

编写干净且可维护的代码是软件开发的关键方面。干净代码的关键原则之一是接受接口并返回结构。本文探讨了如何在 Go 中实现这一原则,以 UserRepository 为例。

接受接口和返回结构的好处

通过接受接口和返回结构,我们在 Go 代码中实现了多个好处。遵循此原则使 Go 代码更加整洁、可维护和灵活:

  1. 1. 解耦:实现可以轻松替换,而不影响代码的其他部分。

  2. 2. 可测试性:模拟 UserRepository 接口简化了测试。

  3. 3. 可扩展性:添加新的实现或修改现有实现变得更容易管理。

  4. 4. Go 中接口的威力:在 Go 中,接口提供了一种定义类型必须实现的一组方法的方式,而无需指定这些方法的实现方式。这允许更灵活和模块化的代码,使更换实现、测试代码和扩展功能变得更容易。

  5. 5. 代码重用性和可互换性:接受接口并返回结构被认为是干净和惯用的 Go 代码的关键原因之一,是因为它促进了代码的重用性和可互换性。当函数或方法接受接口时,它不与特定实现绑定。这允许开发者在不更改使用接口的代码的情况下替换一个实现。在从一个数据库系统迁移到另一个或重构代码以提高性能时,这尤其有用。

  6. 6. 更简单的测试和模拟:接受接口使测试和模拟代码变得更容易。通过接受接口而不是具体类型,函数或方法在测试期间可以使用模拟实现。这允许开发者隔离正在测试的代码,并控制依赖项的行为,从而导致更准确和可靠的测试。

  7. 7. 封装实现细节:接受接口并返回结构的另一个优点是,它有助于封装实现细节。当函数或方法接受一个接口时,它关注它所需的行为,而不是提供该行为的特定类型。这种抽象有助于分离关注点,使代码更加模块化和可维护。

  8. 8. 多态性和组合:Go 中的接口支持多态性,允许基于它们实现的方法将不同的类型视为相同。这促进了组合优于继承,因为可以将不同的类型组合在一起以实现所需的功能。通过接受接口,函数和方法可以与实现所需方法的任何类型一起工作,从而轻松组合新类型或扩展现有类型。

  9. 9. 组件之间的清晰合同:通过定义和接受接口,在组件之间建立了清晰的合同。此合同指定了方法及其签名,确保组件遵循特定的行为。这有助于创建更稳健和可维护的代码库,因为对一个组件的更改不会意外地破坏依赖于相同接口的另一个组件。

Go 中的示例

定义 UserRepository 接口

第一步是创建一个 UserRepository 接口,该接口定义了任何具体实现所期望的方法。在这个例子中,我们有一个简单的 User 结构和一个 GetUserByID 方法。

package repository

type User struct {
 ID    int64  `json:"id"`
 Name  string `json:"name"`
 Email string `json:"email"`
}

type UserRepository interface {
 GetUserByID(id int64) (*User, error)
}

使用 MongoDB 实现 UserRepository 接口

为了为 MongoDB 后端实现 UserRepository 接口,我们创建了一个 MongoUserRepository 结构体,它嵌入了 *mongo.Collection 并实现了 GetUserByID 方法。

package mongo_repo

import (
 "accept-interfaces/repository"
 "context"
 "go.mongodb.org/mongo-driver/bson"
 "go.mongodb.org/mongo-driver/mongo"
)

type MongoUserRepository struct {
 collection *mongo.Collection
}

func (r *MongoUserRepository) GetUserByID(id int64) (*repository.User, error) {
 filter := bson.M{"id": id}
 user := &repository.User{}
 err := r.collection.FindOne(context.Background(), filter).Decode(user)
 if err != nil {
  return nil, err
 }
 return user, nil
}

func NewMongoUserRepository(collection *mongo.Collection) *MongoUserRepository {
 return &MongoUserRepository{collection: collection}
}

使用 PostgreSQL 实现 UserRepository 接口

同样地,对于 PostgreSQL 后端,我们创建了一个 PgxUserRepository 结构体,它嵌入了 *pgx.Conn 并实现了 GetUserByID 方法。

package postgres_repo

import (
 "accept-interfaces/repository"
 "context"
 "github.com/jackc/pgx/v4"
)

type PgxUserRepository struct {
 conn *pgx.Conn
}

func (r *PgxUserRepository) GetUserByID(id int64) (*repository.User, error) {
 query := "SELECT id, name, email FROM users WHERE id = $1"
 user := &repository.User{}
 err := r.conn.QueryRow(context.Background(), query, id).Scan(&user.ID, &user.Name, &user.Email)
 if err != nil {
  return nil, err
 }
 return user, nil
}

func NewPgxUserRepository(conn *pgx.Conn) *PgxUserRepository {
 return &PgxUserRepository{conn: conn}
}

在 main 中展示

在下面提供的 main 包中,我们通过实现 UserRepository 接口,将我们为 PostgreSQL 和 MongoDB 仓库创建的组件组合在一起。其工作原理如下:

  1. 1. 初始化 PostgreSQL 连接:我们首先使用带有包含所需数据库连接详细信息的 DSN 字符串的 pgx.Connect 函数创建到 PostgreSQL 数据库的连接。

  2. 2. 为 PostgreSQL 创建一个 UserRepository 实例:我们通过调用 postgres_repo.NewPgxUserRepository 函数并传递 PostgreSQL 连接作为参数来实例化一个新的 PgxUserRepository

  3. 3. 初始化 MongoDB 连接:我们使用带有包含所需 MongoDB 连接详细信息的 URI 字符串的 mongo.Connect 函数建立到 MongoDB 服务器的连接。

  4. 4. 为 MongoDB 创建一个 UserRepository 实例:我们通过调用 mongo_repo.NewMongoUserRepository 函数并传递 MongoDB 集合作为参数来实例化一个新的 MongoUserRepository

  5. 5. 从选定的数据库中获取一个用户:我们定义一个 userID 变量,值为 1,并调用 getUserFromDatabase 函数两次,每个 UserRepository 实例 (PostgreSQL 和 MongoDB) 调用一次。该函数根据传递给它的 UserRepository 实例的类型来确定使用哪个仓库。

  6. 6. 显示获取的用户:从每个数据库获取用户后,我们将用户详细信息打印到控制台。

getUserFromDatabase 函数负责根据传递给它的 UserRepository 实例选择正确的仓库实现。如果实例是 *postgres_repo.PgxUserRepository 类型,它将在 PostgreSQL 仓库上调用 GetUserByID 方法。如果实例是 *mongo_repo.MongoUserRepository 类型,它将在 MongoDB 仓库上调用 GetUserByID 方法。

通过遵循这种方法,我们可以创建干净、模块化且易于维护的代码,同时遵循 UserRepository 接口的契约,与不同类型的数据库一起工作。

package main

import (
 "context"
 "fmt"
 "github.com/jackc/pgx/v4"
 "log"
 "time"

 "accept-interfaces/repository"
 "accept-interfaces/repository/mongo_repo"
 "accept-interfaces/repository/postgres_repo"
 "go.mongodb.org/mongo-driver/mongo"
 "go.mongodb.org/mongo-driver/mongo/options"
)

func main() {
 time.Sleep(10 * time.Second)
 // Create a UserRepository instance for PostgreSQL
 dsn := fmt.Sprintf("host=localhost port=5432 user=postgres password=postgres dbname=db sslmode=disable")
 pgConn, err := pgx.Connect(context.Background(), dsn)
 if err != nil {
  log.Fatalf("Unable to connect to PostgreSQL: %v", err)
 }
 pgUserRepo := postgres_repo.NewPgxUserRepository(pgConn)

 // Create a UserRepository instance for MongoDB
 client, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://root:pass@localhost:27017/"))
 if err != nil {
  log.Fatalf("Unable to connect to MongoDB: %v", err)
 }
 mongoUserRepo := mongo_repo.NewMongoUserRepository(client.Database("test").Collection("users"))

 // Fetch a user from the selected database
 userID := int64(1)

 user, err := getUserFromDatabase(pgUserRepo, userID)
 if err != nil {
  log.Fatalf("Error fetching user: %v", err)
 }
 fmt.Printf("User from PostgreSQL: %+v\n", user)

 user, err = getUserFromDatabase(mongoUserRepo, userID)
 if err != nil {
  log.Fatalf("Error fetching user: %v", err)
 }
 fmt.Printf("User from MongoDB: %+v\n", user)
}

func getUserFromDatabase(repo repository.UserRepository, id int64) (*repository.User, error) {
 switch r := repo.(type) {
 case *postgres_repo.PgxUserRepository:
  return r.GetUserByID(id)
 case *mongo_repo.MongoUserRepository:
  return r.GetUserByID(id)
 default:
  return nil, fmt.Errorf("unsupported repository type")
 }
}

我用于测试的 docker-compose 文件。

version: "3.9"

services:
  postgres:
    image: postgres:14-alpine
    container_name: postgres
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: db
    volumes:
      - ./postgres-data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

  mongodb:
    image: mongo:5.0.6-focal
    container_name: mongodb
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: pass
      MONGO_INITDB_DATABASE: db
    volumes:
      - ./mongo-data:/data/db
    ports:
      - "27017:27017"

总结

在Go中接受接口并返回结构体是写出清晰、惯用代码的关键原则。它鼓励代码的可复用性、互换性、封装性和组合性,同时使测试和模拟变得更加容易。通过充分利用Go中的接口,开发者可以创建更易维护、灵活和健壮的代码库。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
众里寻它千百度:周正中(德哥)细数从Oracle到PG这些年的摸索与发现
,通过 MongoDB 推动 NoSQL(第3部分)
如何从spring-data mongodb扩展SimpleMongoRepository?
mongodb的简单安装和配置
go.mongodb.org/mongo-driver/mongo string转bson
在CentOS中使用 yum 安装MongoDB及服务器端配置
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服