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)
}
评论区