likes
comments
collection
share

一次 Golang 优化编程思考

作者站长头像
站长
· 阅读数 15

背景

刚开始使用 Golang 进项目开发时,因为在开发时对一些理解没有足够的透彻,导致整个项目代码出现过多的冗余以及扩展性比较差。本文主要陈述在使用的Golang 时的一些结构设计思考的记录。

初始设计

刚开始构建项目框架时,主要讲整个结构划分了四层,详细可查看之前记录的文章:Golang 项目目录结构设计思考 - 掘金

-- handler (其实就是 controller)
-- service (核心:主要处理业务路基)
-- repository (提供和数据库操作基本方法 CRUD 方法)
-- entities(与数据库表对应)

在 controller 层中主要处理参数相关内容不进行任何核心业务的处理,在 service 主要处理核心业务逻辑功能,在 repository 层中没有业务流程主要和数据库交互完成基本的 CRUD 功能。但是在最开始使用的时候,没有充分利用 golang 接口的特性,基本就是方法集合堆积。

基于这样的四层设计其实和 Java 的有点像,各层级功能明确,以便在后续方便扩展。一个用户请求时从路由到 controller 接收参数,然后到 service 执行真正的业务逻辑,再与 DB 进行数据库完成 CRUD 的工作。

func List() (list []entities.User){
	// to do something
	return list
}

func FindById() (user entities.User) {
	// to do something
	return user
}

使用接口

Golang 的接口提供了一种约定,定义了对象应该具备的行为,而不关心对象的具体类型,通过使用接口就可以实现更易于重构和维护的代码结构。

type IUserRepo interface {
	List() (list []entities.User)
	FindById() (user entities.User)
}

type UserRepo struct {
	Db *gorm.DB
}

func (u *UserRepo) List() (list []entities.User) {
	// to do something
	db := u.Db.Model(&entities.User{})
	if err := db.Find("").Error; err !=nil {
		println("err")
	}
	return list
}

func (u *UserRepo) FindById() (user entities.User) {
	// to do something
	return user
}

但是接口是针对一系列实体有相同行为时,通过接口约定相同的行为,然后让不同的实体类型来实现这个接口。其灵活性和可扩展性就是在这点上体现,所以接口的使用场景包括但不限于以下情况:

  1. 定义插件系统:通过定义接口,可以让不同的插件实现相同的接口,以便在程序运行时动态加载和使用插件功能。比如:一个系统可能要接mysql 、orl数据库
  2. 实现依赖注入:通过接口,可以定义组件的行为,并在其他组件中注入具体的实现。这样可以实现松耦合的组件之间的交互。
  3. 编写可测试的代码:通过依赖注入和接口的使用,可以轻松地为单元测试提供虚拟的实现,以便更容易编写和执行测试。

各模块行为不一致,怎么办?

如果各个模块的行为完全不一致,使用接口可能会显得过于累赘,并且不符合设计原则。接口的设计应该基于模块之间的共享行为和约定。那么对于各模块行为都不一致的,可能需要使用不同设计模式:

  1. 继承和多态:使用继承和多态的概念,可以针对不同的模块创建不同的类或对象,并根据其特定的行为来调用相应的方法。这样可以避免强制使用相同的接口。
  2. 策略模式:使用策略模式可以为不同的模块定义不同的策略类,每个策略类实现特定的行为。然后在运行时选择适当的策略来执行相应的行为。
  3. 条件判断:如果只有少数几个模块具有不同的行为,可以使用条件判断来处理。根据条件的不同,执行相应的逻辑。
// 比如 NoBehavorRepo 可能没有一些行为,那么通过将IUserRepo作为实体的一部分,然后在执行的使用通过条件判断是否
type NoBehavorRepo struct {
	userRepo IUserRepo
}

func (bh NoBehavorRepo) DoListAction() {
	if bh.userRepo != nil {
		bh.userRepo.List()
	} else {
		fmt.Println("没有指定行为")
	}
}

使用 New 函数

进一步优化,使用 New 函数封装实体。这种方式可以将实例化对象的细节封装起来,这样调用者就无需了解对象创建过程。后续在对象创建修改和扩展,调用者也无需关心,降低来代码的耦合性等等还有很多的好处。

type userRepo struct {
	Db *gorm.DB
}

func NewUserRepo(db *gorm.DB) IUserRepo {
	return &userRepo{
		Db: db,
	}
}

使用 New 函数隐藏了userRepo对象的实现细节,而且如果需要增加 userRepo 属性时,上层调用者都无需关注。这种方式不仅提高代码的封装性和抽象性,而且让系统更容易进行扩展和维护。这也是一种常见的对象创建模式。

使用依赖注入

参数注入

使用New函数后在,接下来就可以在每层之间就可以使用依赖注入方式。通过注入所需要的依赖项完成相关功能。从而使得代码和具体的实现解耦。比如 service 层需要 注入 repo 层的实体。

// service 层定义了结构体,并将依赖的repo作为其 
type UserService struct {
	userRepo repository.IUserRepo
	roleRepo repository.IRoleRepo
}

func NewUserService(userRepo UserRepo, roleRepo RoleRepo) *UserService {
	return &UserService{
		userRepo,
		roleRepo,
	}
}

New函数手动实例化

如果依赖项很多的话,就会导致 NewUserService 接收到的参数很多。所以我使用在 NewUserService 函数中手动实例化依赖项。这种会增加代码的复杂性。

func NewUserService2() *UserService {
	db := initDB()

	userRepo := repository.NewUserRepo(db)
	roleRepo := repository.NewRoleRepo(db)
	return &UserService{
		userRepo,
		roleRepo,
	}
}

Options 模式

所以可以考虑使用 Options 模式进一步优化。Options 模式通过使用可选参数对象来传递参数,使函数调用更具可读性并提供了更多灵活性。

// 定义一个 Options 结构体,该结构体包含函数的可选参数:
type UserServiceOptions struct {
	UserRepo UserRepo
	RoleRepo RoleRepo
}

type UserServiceOption func(*UserServiceOptions)

// 在 NewUserService 函数中接受可选参数,并将其应用于 UserService 结构体:
func NewUserService(options ...UserServiceOption) *UserService {
	// 默认参数
	opts := &UserServiceOptions{
		UserRepo: nil,
		RoleRepo: nil,
	}

	// 应用可选参数
	for _, opt := range options {
		opt(opts)
	}

	// 创建 UserService 并设置依赖项
	userService := &UserService{
		userRepo: opts.UserRepo,
		roleRepo: opts.RoleRepo,
	}

	return userService
}

// 定义一些可选参数函数,用于设置 Options 结构体中的字段:
func WithUserRepo(userRepo UserRepo) UserServiceOption {
	return func(opts *UserServiceOptions) {
		opts.UserRepo = userRepo
	}
}

func WithRoleRepo(roleRepo RoleRepo) UserServiceOption {
	return func(opts *UserServiceOptions) {
		opts.RoleRepo = roleRepo
	}
}
// 具体使用时就可以选择性传递参数
userService := NewUserService(
	WithUserRepo(userRepo),
	WithRoleRepo(roleRepo),
)

记录一切努力而又美好的经历, 我是小雄Ya!!!