目 录CONTENT

文章目录

Golang Option模式和链式调用

Sakura
2024-08-17 / 4 评论 / 3 点赞 / 72 阅读 / 0 字 / 正在检测是否收录...
温馨提示:
部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

1. Option 模式

1.1 函数形式

type User struct {
	Name string
	Age  int
	Tags map[string]string
}

// UserOption 第一种写法,函数写法
type UserOption func(u *User)

func WithName(name string) UserOption {
	return func(u *User) {
		u.Name = name
	}
}

func WithAge(age int) UserOption {
	return func(u *User) {
		u.Age = age
	}
}

func WithTag(k, v string) UserOption {
	return func(u *User) {
		if u.Tags == nil {
			u.Tags = make(map[string]string)
		}
		u.Tags[k] = v
	}
}

// 默认用户实现
func DefaultUser() *User {
	return &User{
		Name: uuid.NewString(),
		Age:  0,
	}
}

func NewUser(options ...UserOption) *User {
	// 这里还可以加一个默认user实现,用于用户不进行任何option的
	defaultUser := DefaultUser()
	// 遍历的时候直接对defaultUser进行修改
	//user := &User{}
	// 遍历函数的时候就已经执行了
	for _, option := range options {
		// 这个option其实就是WithName和WithAge的返回值
		// 也就是UseOption这个函数
		// 所以需要参数*User
		option(defaultUser)
	}
	return defaultUser
}

func main() {
	// 使用函数写法定义的 option 模式
	user := NewUser(WithName("Sakura"), WithAge(20), WithTag("k1", "v1"))
	fmt.Println(user)
	// 不传入任何 option 的 user
	user2 := NewUser()
	fmt.Print(user2)
}

1.2 接口形式(Uber 推荐这种写法)

Uber 在 uber-go/guide 中推荐使用接口形式的 option,而不是函数形式的 option

我们建议实现此模式的方法是使用一个 Option 接口,该接口保存一个未导出的方法,在一个未导出的 options 结构上记录选项。

我们相信上面的模式为作者提供了更多的灵活性,并且更容易对用户进行调试和测试。特别是,在不可能进行比较的情况下它允许在测试和模拟中对选项进行比较。此外,它还允许选项实现其他接口,包括 fmt.Stringer,允许用户读取选项的字符串表示形式。

type User struct {
	Name string
	Age  int
	Tags map[string]string
}

// UserOption 接口形式定义 UserOption
type UserOption interface {
	apply(*User)
}

// 如果是基本数据类型,直接在自定义类型就行
// 也可以定义为结构体
type nameOption string

func (n nameOption) apply(userOption *User) {
	userOption.Name = string(n)
}

// 如果是复杂数据类型,需要定义一个结构体,然后实现接口方法
// 假设 ageOption 是一个包含多个配置的结构体
type ageOption struct {
	age int
}

func (a ageOption) apply(userOption *User) {
	userOption.Age = a.age
}

type tagOption struct {
	k string
	v string
}

func (t tagOption) apply(userOption *User) {
	if userOption.Tags == nil {
		userOption.Tags = make(map[string]string)
	}
	userOption.Tags[t.k] = t.v
}

func WithName(name string) UserOption {
	return nameOption(name)
}

func WithAge(age int) UserOption {
	return ageOption{age: age}
}

func WithTag(key, value string) UserOption {
	return tagOption{k: key, v: value}
}


func DefaultUser() *User {
	return &User{
		Name: "default", // 或者uuid.String
		Age:  18,
		Tags: make(map[string]string),
	}
}

func NewUser(opts ...UserOption) *User {
	user := DefaultUser()
	
	for _, opt := range opts {
		opt.apply(user)
	}

	return user
}
func TestNewUser(t *testing.T) {
	user := NewUser(WithAge(18), WithName("Sakura"), WithTag("tag", "value"))
	fmt.Println(user)
	user2 := NewUser()
	fmt.Println(user2)
}

虽然接口形式比函数形式写法上复杂了不少,但也多了许多优点

  • 可比较性

假设我们想要比较两个 UserOption 是否相同。在接口形式下,可以通过实现一个方法来比较它们。

type nameOption struct {
	name string
}

func (n nameOption) apply(opts *UserOptions) {
	opts.Name = n.name
}

// 新增一个方法用于比较两个 nameOption 是否相等
func (n nameOption) isEqual(other UserOption) bool {
	if no, ok := other.(nameOption); ok {
		return n.name == no.name
	}
	return false
}
  • 可读性和可调试性

可以让 UserOption 实现 fmt.Stringer 接口

import "fmt"

// UserOption 接口形式定义 UserOption
type UserOption interface {
	apply(*User)
	// String 实现 fmt.Stringer 接口
	String() string
}

// 实现 fmt.Stringer 接口
func (n nameOption) String() string {
	return fmt.Sprintf("nameOption{name: %s}", n.name)
}

func (a ageOption) String() string {
	return fmt.Sprintf("ageOption{age: %d}", a.age)
}

func (t tagOption) String() string {
	return fmt.Sprintf("tagOption{key: %s, value: %s}", t.key, t.value)
}
  • 可拓展性

还可以为 UserOption 添加更多行为。例如,可以添加一个 Validate 方法来检查 Option 是否有效。

// 为 UserOption 添加 Validate 方法
type UserOption interface {
	apply(*UserOptions)
	// 可选:实现 String() 方法
	String() string
	// 可选:实现 Validate() 方法
	Validate() error
}

func (n nameOption) Validate() error {
	if len(n.name) == 0 {
		return fmt.Errorf("name cannot be empty")
	}
	return nil
}

func (a ageOption) Validate() error {
	if a.age < 0 {
		return fmt.Errorf("age cannot be negative")
	}
	return nil
}

func (t tagOption) Validate() error {
	if len(t.key) == 0 || len(t.value) == 0 {
		return fmt.Errorf("tag key and value cannot be empty")
	}
	return nil
}

在初始化 User 的时候检查传入参数的合法性

func NewUser(opts ...UserOption) (*User,error) {
	user := DefaultUser()
	
	for _, opt := range opts {
        // 检查完是否合法之后,再进行 apply
		err := opt.Validate()
		if err != nil {
			return nil, err
		}
		opt.apply(user)
	}
	return user,nil
}

1.3 检索过滤的 option 模式

type QueryOption interface {
	// Apply 用于执行筛选条件
	Apply([]*User) []*User
}

// QueryUser 重点
// 把初始用户集合传进来,去过每一个option
func QueryUser(users []*User, options ...QueryOption) []*User {
	resultUsers := users
	for _, option := range options {
		// 这个执行的是实现每个QueryOption接口的Apply方法
		// 挨个筛选,where和limit
		resultUsers = option.Apply(resultUsers)
	}
	return resultUsers
}

type Where struct {
	Name    string
	FromAge int
	ToAge   int
}

func (w Where) Apply(users []*User) []*User {
	resultUsers := make([]*User, 0, len(users))
	for _, user := range users {
		if user.Name == w.Name {
			resultUsers = append(resultUsers, user)
		}
	}
	return resultUsers
}

type limit struct {
	Offset int
	Count  int
}

func (l limit) Apply(users []*User) []*User {
	if l.Offset >= len(users) {
		return nil
	}
	if l.Offset+l.Count >= len(users) {
		return users[l.Offset:]
	}

	return users[l.Offset : l.Offset+l.Count]
}
func TestNewUser(t *testing.T) {
	user := NewUser(WithName("Sakura"), WithAge(11))
	user1 := NewUser(WithName("Sakuras"), WithAge(12))
	user2 := NewUser(WithName("Sakura"), WithAge(13))
	user3 := NewUser(WithName("Sakura"), WithAge(14))
	user4 := NewUser(WithName("Sakura"), WithAge(15))
	user5 := NewUser(WithName("Sakura"), WithAge(16))
	user6 := NewUser(WithName("Sakura"), WithAge(17))

	fmt.Println(user)

	users := []*User{user, user1, user2, user3, user4, user5, user6}
	queryUser := QueryUser(users, &Where{
		Name: "Sakura",
	}, limit{
		Offset: 0,
		Count:  3,
	})

	for _, u := range queryUser {
		fmt.Println(*u)
	}
}

2. 链式调用

// User 定义User结构体
type User struct {
	name  string
	age   int
	email string
}

// WithName 添加链式调用方法
func (u *User) WithName(name string) *User {
	u.name = name
	return u
}

func (u *User) WithAge(age int) *User {
	u.age = age
	return u
}

func (u *User) WithEmail(email string) *User {
	u.email = email
	return u
}

// 初始化User的方法
func NewUser() *User {
	return &User{}
}

func main() {
	// 链式调用创建用户
	newUser := NewUser().
		WithName("Alice").
		WithAge(25).
		WithEmail("alice@example.com")

	fmt.Printf("New user created: %+v", newUser)
}

3

评论区