生成器模式是一种创建型设计模式,使你能够分步骤创建一个复杂对象。该模式还允许你使用相同的创建代码生成不同形式和不同类型的对象,下面通过几个方面具体的说明。
解决的问题
现在有一个复杂对象,包括很多的成员变量和嵌套对象,如何初始化这个复杂的对象呢?
通常就是将这些初始化代码放入一个构造方法当中,当然也有可能会放在多个不同的构造方法中
但是,如果将所有的参数都放到一个构造方法中,那么就会有很多冗余的参数;而要是使用多个不同的构造方法的话,不同的参数组合会产生数量不可控的构造方法
不管是使用一个构造方法还是使用多个构造方法,都会在一定程度上增加构造这个对象复杂度,并且会导致这个对象很难扩展,下面使用一个具体的案例来解释一下
比如,现在要构建一个House
对象,它包含的基本属性有:door,window,roof,wall,floor,light 等,并且它也可以包含一些其他的设施,如:heating,electricity,gas,yard,pool,soft 等等,那如何构造这个对象呢?如何为不同的人构造不同的House
呢?
最简单的就是在这个House
对象的基础上进行扩展,添加pool
,soft
,heating
等等这些对象;最简单的方法就是添加一个超级构造方法将所有的这些属性和嵌套对象都包含进来,但是,这样的话即便构造一个简单House
也需要传入很多的构造参数,尽管大部分构造参数根本用不到
当然,还有一种方法就是添加多个构造方法,不同的构造方法构造不同形式的House
,但这随着House
的属性不断增加,就会导致构造方法的数量不可控了,并且使得代码的扩展性极差
解决方案
生成器模式建议将对象构造的代码从产品类中抽取出来,并将其放在一个名为生成器的独立对象当中;将对象的构造划分为一组步骤,如:buildWall()
创建墙面,buildDoor
创建门等等;
每次创建对象的时候,你可以通过生成器执行一系列步骤;重点就是,你无需调用所有的步骤,只需要调用创建特定对象的那些步骤即可
当然,对于同一类产品可能会有不同的形式,比如:木制的Door
和铁制的door
;在这种情况下,就可以创建多个不同的生成器,用不同方式实现一组相同的创建步骤,然后在客户端使用这些生成器创建不同形式的对象;比如:WoodHouseBuilder
创建木制的房子,IronHouseBuilder
则会给你返回一个铁制的房子
主管
虽然现在解决了超级构造函数的问题,但是客户端还是得知道构建一个House
对象的具体步骤;那有没有一种方法直接生成一个你想要的房子呢?
答案是有的,就是通过主管类,比如:你现在想要一个木制的房子,那么主管类会将一系列创建木制房子的步骤封装起来,当你使用主管类调用时,主管类就会给你返回给你一个木制的房子,这样就不需要再去了解构建一个木制房子的具体细节了
这样,主管类屏蔽了构建一个木制类房子的细节,真正的做到了客户端和具体的构建对象之间的解耦,更加的利于程序的扩展了
生成器模式结构图和代码
代码示例:
package main
import "fmt"
type House struct {
Wall string
Door string
Window string
Heating string
Yard *Yard
}
type Yard struct {
Size int
Name string
}
func newYard(size int, name string) *Yard {
return &Yard{
Size: size,
Name: name,
}
}
func (h *House) String() string {
s := fmt.Sprintf("Door:%s,Wall:%s,Window:%s,Heating:%s",h.Door,h.Wall,h.Window,h.Heating)
if h.Yard != nil{
s = fmt.Sprintf("%s,yard size:%d,yard name:%s",s,h.Yard.Size,h.Yard.Name)
}
return s
}
HouseBuilder
package main
type HouseBuilder interface {
BuildWall()
BuildYard()
BuildDoor()
BuildWindow()
BuildHeating()
// 如果是同一类型不同形式的对象,则在此处声明获取产品的方法即可
// 如果是不同类型的对象,则需要在具体的 builder 中添加获取该类型产品的方法
GetHouse() *House
}
// woodBuilder use build wood house
type woodBuilder struct {
wall string
door string
window string
heating string
yard *Yard
}
func newWoodBuilder() HouseBuilder {
return &woodBuilder{}
}
func (w *woodBuilder) BuildWall() {
w.wall = "white wall"
}
func (w *woodBuilder) BuildDoor() {
w.door = "red wood"
}
func (w *woodBuilder) BuildYard() {
w.yard = newYard(10,"wood yard")
}
func (w *woodBuilder) BuildWindow() {
w.window = "wood window"
}
func (w *woodBuilder) BuildHeating() {
w.heating = "simonzi"
}
func (w *woodBuilder) GetHouse() *House {
return &House{
Wall: w.wall,
Door: w.door,
Window: w.window,
Heating: w.heating,
Yard: w.yard,
}
}
Director
package main
type director struct {
builder HouseBuilder
}
func newDirector(builder HouseBuilder) *director {
return &director{builder: builder}
}
func (d *director) SetBuilder(builder HouseBuilder) {
d.builder = builder
}
func (d *director) buildWoodHouse() *House {
d.builder.BuildDoor()
d.builder.BuildHeating()
d.builder.BuildWall()
d.builder.BuildWindow()
d.builder.BuildYard()
return d.builder.GetHouse()
}
客户端代码
package main
import "fmt"
func main() {
houseBuilder := newWoodBuilder()
director := newDirector(houseBuilder)
house := director.buildWoodHouse()
fmt.Printf("%s",house)
}
上面创建House
对象是通过指定的HouseBuilder
创建的,House
的参数是默认的;如果你想要动态参数,那么就将这些参数传入到WoodBuild
就可以了,因为在这已经确定了这些参数是创建这个所必须的,所以不会出现参数的冗余问题;
对于扩展性而言,对于新增的字段则只需要再新加一个新的生成器就可以了,这样就不需要更改之前的代码就完成了程序的扩展了