XingPiaoLiang's

Back

生成器模式#

生成器模式就是我们常在项目源码中见到的,使用Builder创建新对象的形式,这次来揭开Builder 他的面纱。

问题示例#

现在,我们需要创建一个House房屋对象。房屋对象当然就分了很多种,可以是豪华的带庭院、带游泳池的;又或者只是单单是一个公寓。又或者,从另外一个角度来看,还有着南方北方之间(供暖设备、暖气、排水等等其他设施)的差异。

问题分析#

对于一个基础的House房屋,墙和房顶,是必不可少的。毫无疑问的,还会存在很多选配的设施,供电系统、供水系统等等等。但是这都是不确定的,也就是在构造函数中存在可选参数, 如果仅仅使用一个构造函数的话就会存在大量的冗杂的参数,并且即使是使用支持方法重载的语言(Java、C#等)也会出现下面诡异的现象:

class House {
    House(int size) { …… }
    House(int size, boolean charge) { …… }
    House(int size, boolean charge, boolean water) { …… }
}
java

所以,Builder(生成器模式)应运而生了。

使用Builder解决#

生成器模式的主要思路就是将整个产品的生产步骤的每一步都抽离出来,抽离进一个Builder对象中。例如一个房屋对象的Builder的架构如下图示: 生成器模式会将对象的构造过程划分成一系列独立的步骤,每次创建对象时,你只需要使用Builder中的一系列步骤来创建对象。这样的好处在于你并没有被强制调用所有步骤,而是可以根据自己的需要使用特定的步骤

同时,当你需要创建一个创建步骤和之前的对象相同,但是具体的实现细节不同时,你可以创建多个生成器,他们共同实现一个生成器接口。该基生成器接口定义一组相同的创建步骤。例如:木头房屋的大门需要使用木头建造,宫殿房屋的大门需要使用钻石石头建造。

主管#

除了Builder这一个干活的角色以外,当然还可以抽象出一个主管的角色用于控制Builder的行为。因为Builder的生产步骤不是固定的,所以可以通过引入Director这一角色,用于对Builder的建造步骤进行协调。确定整个生产的流程。当然,这也是可选的,因为在客户端处按照自己的需求步骤,按特定顺序使用Builder对对象进行创建。 整个生成器模式结构如下:

具体代码实现#

我们要创建一个iglooHouseWoodHouse

package builder

import (
	"fmt"
	"testing"
)

// basic builder interface
type IBuilder interface {
	setRoof()
	setSize()
	// 直接返回实例对象
	build() House
}

// 拿到特定的builder
func getHouseBuilder(t string) IBuilder {
	switch t {
	case "igloo":
		return &IglooBuilder{}
	case "wood":
		return &WoodBuilder{}
	default:
		return &WoodBuilder{}
	}
}

type IglooBuilder struct {
	House
}

type WoodBuilder struct {
	House
}

func (b *WoodBuilder) build() House {
	house := House{
		Size: b.Size,
		Roof: b.Roof,
	}
	return house
}

// 注意这里的IglooHouse的建造过程就不同于WoodBuilder了,多了一个Pool
func (b *IglooBuilder) build() House {
	b.setPool()
	house := House{
		Size: b.Size,
		Roof: b.Roof,
		Pool: b.Pool,
	}
	return house
}

type House struct {
	Roof string
	Size int
	Pool string
}

func (b *WoodBuilder) setRoof() {
	b.Roof = "woodRoof"
}

func (b *WoodBuilder) setSize() {
	b.Size = 200
}

func (b *IglooBuilder) setPool() {
	b.Pool = "Ice Pool"
}

func (b *IglooBuilder) setRoof() {
	b.Roof = "iceRoof"
}

func (b *IglooBuilder) setSize() {
	b.Size = 2000
}

type Director struct {
	b IBuilder
}

func newDirector() *Director {
	return &Director{}
}

func (d *Director) SetBuilder(builder IBuilder) {
	d.b = builder
}

func (d *Director) BuildHouse() House {
	d.b.setRoof()
	d.b.setSize()
	return d.b.build()
}

func TestBuilder(t *testing.T) {
	iglooBuilder := getHouseBuilder("igloo")
	woodBuilder := getHouseBuilder("wood")
	d := newDirector()
	d.SetBuilder(iglooBuilder)
	igloo := d.BuildHouse()
	fmt.Printf("igloo: %#v\n", igloo)

	d.SetBuilder(woodBuilder)
	wood := d.BuildHouse()
	fmt.Printf("wood: %#v\n", wood)
    /**
    igloo: builder.House{Roof:"iceRoof", Size:2000, Pool:"Ice Pool"}
    wood: builder.House{Roof:"woodRoof", Size:200, Pool:""}
    */
}
go

Builder总结#

  • 如上文所提到,生成器模式可以很好的避免telescoping constructor(重叠构造函数)的出现。生成器模式能够让你很好的分离创建步骤
  • 考虑创建主管类。 它可以使用同一生成器对象来封装多种构造产品的方式。
  • 只有在所有产品都遵循相同接口的情况下, 构造结果可以直接通过主管类获取。 否则, 客户端应当通过生成器获取构造结果。(如代码示例中:igloo存在Pool而woodHouse并没有)
  • 缺点:需要创建的类变多了,普通小项目可能变得更加复杂
设计模式-创建型-生成器模式
https://astro-pure.js.org/blog/builder
Author erasernoob
Published at May 19, 2025
Comment seems to stuck. Try to refresh?✨