9. 函数式选项模式

Golang中的函数式构造函数与DB查询优化

如何让golang提供优雅的构造函数呢?

golang是不支持函数重载的,如果想为结构体实现不同程度的初始化,就需要提供不同的构造函数,如下代码

package main

import "fmt"

type Book struct {
	Title string
	ISBN string
}

func NewBook(title string) *Book {
	return &Book{
		Title: title,
	}
}

func NewBook2(title,isbn string) *Book{
	return &Book{
		Title: title,
		ISBN:  isbn,
	}
}

func main() {
	book := NewBook("Golang")
	fmt.Println(book)
	book = NewBook2("Golang","1234")
	fmt.Println(book)
}

输出:
&{Golang }
&{Golang 1234}

如果Book有十多个属性,是否给某个字段赋初值,排列组合则需要非常多的NewBookX方法

此时可以使用函数式选项模式,不仅可以给一些默认值,还可以灵活的给需要的字段赋值

package main

import "fmt"

type Book struct {
	Title string
	ISBN string
}

//  如果book有十多个属性,是否给某个字段赋初值,排列组合则需要非常多的NewBookX方法
// 此时可以使用函数式选项模式,不仅可以给一些默认值,还可以灵活的给需要的字段赋值
// 函数的参数为结构体指针,从而可以就地修改结构体中的字段
type option func(book *Book)

func WithTitle(title string) option{
	return func (book *Book){
		book.Title = title
	}
}

func WithISBN(isbn string) option{
	return func (book *Book){
		book.ISBN = isbn
	}
}

func NewBook(ops ...option) *Book{
	// book 可以有默认值
	book := &Book{
		Title: "Java",
		ISBN:  "",
	}

	// 通过每个option,给book相应的字段赋值,由于是指针,所以是就地修改
	for _,op := range ops{
		op(book)
	}
	return book
}

func main() {
	book = NewBook(WithTitle("Golang"),WithISBN("5678"))
	fmt.Println(book)

}

输出:
&{Golang 5678}

按照这种方式实现的构造函数,可以高度灵活的给希望赋值的字段赋值,即使Book结构体新增了字段类型,我们也只需要新增一个WithXXX函数即可,符合设计模式中的开闭原则,此外,NewBook在使用时,各个WithXXX作为参数传递的顺序也完全不用关心,不像常规构造函数一样,传参的顺序必须一一对应。

使用函数式编程的第二个重要场景就是使用Gorm框架查询DB时,我们可以通过函数式选项模式灵活的构造查询条件。

首先来看一个大家最容易想到的和数据库交互的代码

package main

import "gorm.io/gorm"

var db *gorm.DB

func InitDB() {
	// 一般会根据读取的配置文件初始化DB实例
}

func GetDB() *gorm.DB {
	return db
}

// model
type Book struct {
	gorm.Model
	Title    string `gorm:"column:title"`
	ISBN     string `gorm:"column:ISBN"`
	Category int    `gorm:"column:category"`
}

func GetBookById(id int) (Book, error) {
	var book Book
	db := GetDB()
	res := db.Where("id = ?", id).First(&book)
	return book, res.Error
}

func GetBookByTitle(title string)(Book,error){
	var book Book
	db := GetDB()
	res := db.Where("title = ?", title).First(&book)
	return book,res.Error
}

func GetBookByISBN(ISBN string)(Book,error){
	var book Book
	db := GetDB()
	res := db.Where("ISBN = ?", ISBN).First(&book)
	return book,res.Error
}

func GetBookByCategory(category int) ([]Book,error){
	var books []Book
	db := GetDB()
	res := db.Where("category = ?",category).Find(&books)
	return books,res.Error
}


// 客户端使用代码
func main(){
	InitDB()
	_,_ = GetBookByTitle("Golang")
	_,_ = GetBookByISBN("1234")
	_,_ = GetBookByCategory(1)
}

咋一看,完全没有问题,可以实现我们的诉求,但是,如果Book新增了字段,就需要再新写对应的GetBookByXXX方法,这还不是重点,重点是如果要通过多个条件查询怎么办,如要根据titlecategory两个字段查询,或者要根据titleISBN两个字段查询,这样组合起来就需要提供非常多的GetBookByXXXAndXXX方法了,显然是很臃肿的。此时函数式选项模式就又派上用场了。

package main

import "gorm.io/gorm"

var db *gorm.DB

func InitDB() {
	// 一般会根据读取的配置文件初始化DB实例
}

func GetDB() *gorm.DB {
	return db
}

// model
type Book struct {
	gorm.Model
	Title    string `gorm:"column:title"`
	ISBN     string `gorm:"column:ISBN"`
	Category int    `gorm:"column:category"`
}

type option func(db *gorm.DB) *gorm.DB

func WithTitle(title string) option {
	return func(db *gorm.DB) *gorm.DB {
		db = db.Where("title = ?", title)
		return db
	}
}

func WithISBN(ISBN string) option {
	return func(db *gorm.DB) *gorm.DB {
		db = db.Where("ISBN = ?", ISBN)
		return db
	}
}

func WithCategoryIn(categories []int) option {
	return func(db *gorm.DB) *gorm.DB {
		db = db.Where("category in (?)", categories)
		return db
	}
}

// GetBookByOptions 和DB交换获取书籍的函数就都请求这个就行了
func GetBookByOptions(ops ...option) ([]Book, error) {
	var books []Book
	db := GetDB()
	for _, op := range ops {
		// 注意gorm中db链式调用后,还需要用db接收
		db = op(db)
	}
	res := db.Find(&books)
	return books, res.Error
}

// 客户端使用代码
func main() {
	InitDB()
	_,_ = GetBookByOptions(WithTitle("Golang"),WithCategoryIn([]int{1,2,3}))
}

对于DB查询,工作中还有一种非常常见的写法,那就是定义一个结构体作为参数,根据字段的内容构造SQL,如下:

package main

import "gorm.io/gorm"

var db *gorm.DB

func InitDB() {
	// 一般会根据读取的配置文件初始化DB实例
}

func GetDB() *gorm.DB {
	return db
}

// model
type Book struct {
	gorm.Model
	Title    string `gorm:"column:title"`
	ISBN     string `gorm:"column:ISBN"`
	Category int    `gorm:"column:category"`
}

// GetBookByOptions 和DB交换获取书籍的函数就都请求这个就行了
func GetBooks(book *Book) ([]Book, error) {
	var books []Book
	db := GetDB()

	if book.Title != "" {
		db = db.Where("title = ?", book.Title)
	}
	if book.ISBN != "" {
		db = db.Where("ISBN = ?", book.ISBN)
	}
	if book.Category > 0 {
		db = db.Where("category in (?)", book.Category)
	}

	res := db.Find(&books)
	return books, res.Error
}

// 客户端使用代码
func main() {
	InitDB()
	book := &Book{
		Title:    "Golang",
	}
	_, _ = GetBooks(book)
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值