配置选项问题
先从创建一个Person说起,有如下一个Person对象,
type Person struct {
Name string
Age int
Weight float32
Addr string
}
拥有三个属性,Name,Age, Addr, 其中Name,Age必填且不能为空,Addr 为可选属性。
可以很容易的写出如下的构造代码。
func NewPersonWithNameAndAge(name string, age int) *Person {
return &Person{Name: name, Age: age}
}
func NewPersonWithNameAndAgeAndAndWeight(name string, age int, weight float32) *Person {
person := NewPersonWithNameAndAge(name, age)
person.Weight = weight
return person
}
func NewPersonWithNameAndAgeAndWeightAndAddr(name string, age int, weight float32, addr string) *Person {
person := NewPersonWithNameAndAgeAndAndWeight(name, age, weight)
person.Addr = addr
return person
}
因为go 不支持函数重载,不得不使用不同的函数名来处理。
配置对象
通常可以很容易想到对可选配置封装成一个配置对象
type Config struct {
Weight float32
Addr string
}
然后Person对象变成这样
type Person struct {
Name string
Age int
Config *Config
}
这样我们就可以使用一个NewPerson构造函数来新建对象了,在对可选属性,新建一个配置对象来填充。
func NewPerson(name string, age int, config *Config) *Person {
return &Person{
Name: name,
Age: age,
Config: config,
}
}
person1 := NewPerson("1", 1, nil)
config := &Config{
Weight: 0,
Addr: "",
}
person2 := NewPerson("2", 2, config)
虽然这么做可以解决多个构造函数的问题,但是Config对象对于Person来说是多余的,并不需要。甚至于在使用可选属性的时候还需要判断 Config 是否为nil。
Builder 模式
如果对Java 熟悉的可以进一步想到使用建造者模式重构。
Person p = new Person.Builder()
.name("aa")
.age(1)
.build();
类似的, 我们可以在go中重构成如下代码
type PersonBuilder struct {
Person
}
func (pb *PersonBuilder) Create(name string, age int) *PersonBuilder {
pb.Person.Name = name
pb.Person.Age = age
return pb
}
func (pb *PersonBuilder) WithWeight(weight float32) *PersonBuilder {
pb.Person.Weight = weight
return pb
}
func (pb *PersonBuilder) WithAddr(addr string) *PersonBuilder {
pb.Person.Addr = addr
return pb
}
func (pb *PersonBuilder) Build() Person {
return pb.Person
}
这样就可以更方便的创建Person对象
pb := PersonBuilder{}
person := pb.Create("1", 1).
WithWeight(60.0).
WithAddr("aa").
Build()
可以很明显的看出来,这种建造者模式不需要额外的Config 对象,但是多了一个PersonBuilder,有没有办法直接在Person上进行build呢?也是可以的,这种就是我们要说的函数式编程。
Functional Options
首先先定义一个Option函数, 接收一个Person对象指针。
type Option func(*Person)
再定义属性设置的函数,返回一个Option函数
func Weight(w float32) Option {
return func(person *Person) {
person.Weight = w
}
}
func Addr(addr string) Option {
return func(person *Person) {
person.Addr = addr
}
}
上面的代码在传入一个参数后返回一个函数。返回的这个函数会设置自己的 Person 属性值。
例如,当调用 Weight(30.1) 时,返回一个如下函数
func(p *Person) {
p.Weight = 30.1
}
有点类似JS中的高阶函数。
现在我们就可以定一个 NewPerson()的函数,其中,有一个可变参数 options ,它可以传出多个上面的函数,然后使用一个 for-loop 来设置我们的 Person 对象。
func NewPerson(name string, age int, options ...Option) *Person {
p := &Person{
Name: name,
Age: age,
}
for _, option := range options {
option(p)
}
return p
}
然后就可以像如下方式创建Person对象
person1 := NewPerson("11", 11)
person2 := NewPerson("22", 22, Weight(22.22))
person3 := NewPerson("33", 33, Addr("33"))
person4 := NewPerson("44", 44, Addr("44"), Weight(44.44))
函数式编程不但解决了使用Config对象方式的时候空判断问题,是放 nil 还是放 Config{}的问题,也不需要额外创建一个 Builder对象。