Functional Options



可变字段结构体问题

想象我们有一个结构体

type Person struct {
	Name     string
	Age      int
	Job      string
	Title    string
	Religion string
	Hobby    string
}

其中:

在这种情况下,当我们创建一个新的对象时,根据不同的情况,我们需要使用不同的参数来创建

func NewPerson(name string, age int8) *Person {
	return &Person{Name: name, Age: age}
}

func NewBoss(name string, age int8, hobby string) *Person {
	return &Person{Name: name, Age: age, Hobby: hobby}
}

func NewWorker(name string, age int8, job string, religion string) *Person {
	return &Person{Name: name, Age: age, Job: job, Religion: religion}
}

Builder模式

为了解决上面的问题,我们可以使用builder模式

type Person struct {
	Name     string
	Age      int8
	Job      string
	Title    string
	Religion string
	Hobby    string
}

type PersonBuilder struct {
	Person
}

func (pb *PersonBuilder) NewPerson(name string, age int8) *PersonBuilder {
	pb.Person.Name = name
	pb.Person.Age = age
	return pb
}

func (pb *PersonBuilder) WithJob(job string, title string) *PersonBuilder {
	pb.Person.Job = job
	pb.Person.Title = title
	return pb
}

func (pb *PersonBuilder) WithReligion(religion string) *PersonBuilder {
	pb.Person.Religion = religion
	return pb
}

func (pb *PersonBuilder) WithHobby(hobby string) *PersonBuilder {
	pb.Person.Hobby = hobby
	return pb
}

func (pb *PersonBuilder) Build() Person {
	return pb.Person
}

这样,我们就可以这样使用。

	pb := PersonBuilder{}
	person := pb.NewPerson("Tom", 18).
		WithJob("boss", "CEO").
		Build()

但是这里多了一个Builder类,只是为了解决一个参数的问题,好像有点大材小用了。在纯解决参数传入问题的场景下,如果我们想省掉这个Builder类,就可以使用Functional Options了

Functional Options

我们可以定义一个函数类型,这个函数只接收struct的指针。

type PersonOption func(*Person)

func WithJob(job string, title string) PersonOption {
	return func(person *Person) {
		person.Job = job
		person.Title = title
	}
}

func WithReligion(religion string) PersonOption {
	return func(person *Person) {
		person.Religion = religion
	}
}

func WithHobby(hobby string) PersonOption {
	return func(person *Person) {
		person.Hobby = hobby
	}
}

然后我们改造创建struct的函数

func NewPerson(name string, age int8, opts ...PersonOption) *Person {
	p := &Person{
		Name: name,
		Age:  age,
	}
	for _, opt := range opts {
		opt(p)
	}
	return p
}

后面我们就可以使用这种方式来创建struct

nobody := NewPerson("Tom", 18)
boss := NewPerson("Bill", 23, WithJob("boss", "CEO"), WithHobby("beauty"))
woker := NewPerson("John", 32, WithJob("farmer", "senior"), WithHobby("study"),WithReligion("Money"))

这样的方式,简洁明了,高内聚,符合直觉,理解简单

参考文档

陈皓 GO 编程模式:FUNCTIONAL OPTIONS