设计模式-生成器模式
前言
无论是在现实世界中还是在软件系统中,都存在一些复杂的对象,他们拥有多个组成部分,以汽车🚗为例,它包括车轮、方向盘、发动机等部件。对于用户而言,无须知道这些部件的装配细节,它几乎不会使用单独部件,而是使用一辆完整的汽车,可以通过生成器模式对其进行设计与描述,生成器模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部构造细节。
在软件开发中,也存在大量类似汽车一样的复杂对象,而对象的属性相当于汽车的部件,建造产品的过程就相当于组合部件的过程。由于组合部件的过程很复杂,因此,这些部件的组合过程往往被“外部化”到一个称作生成器的对象里,生成器返回给客户端的是一个已经建造完毕的完整产品对象,而用户无须关心该对象所包含的属性以及他们的组装方式,这就是生成器模式的愿景。
定义
生成器模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
生成器模式是一步一步创建一个复杂的对象,它允许用户只通过制定复杂对象的类型和内容就可以构建他们,用户不需要知道内部的具体构建细节,建造者模式属于对象创建型。
使用时机
当对象拥有好几种属性,且需要避免构造器伸缩时使用,与工厂模式运用场景不同之处在于:当创建过程仅仅一步到位,使用工厂模式,如果需要分步进行,则考虑使用生成器模式。
本质
生成器模式的本质,就是将构造函数中的参数列表方法化,长长的参数列表,无论是面向对象还是函数式编程,都是大忌,该模式主要就是为了解决该问题。
实现
C++实现
#include <iostream>
class FriedRice {
public:
class FriedRiceBuilder;
void showFlavors() {
std::cout << _size;
if (_egg) std::cout << "-egg";
if (_beef) std::cout << "-beef";
if (_onion) std::cout << "-onion";
std::cout << std::endl;
}
private:
FriedRice(int size):_size(size){}
int _size = 0;
bool _egg = false;
bool _beef = false;
bool _onion = false;
};
class FriedRice::FriedRiceBuilder {
public:
FriedRiceBuilder(int size) {_friedrice = new FriedRice(size);}
FriedRiceBuilder& AddEgg() {_friedrice->_egg = true; return *this;}
FriedRiceBuilder& AddBeef() {_friedrice->_beef = true; return *this;}
FriedRiceBuilder& AddOnion() {_friedrice->_onion = true; return *this;}
FriedRice* Build() {return _friedrice;}
private:
FriedRice* _friedrice;
};
int main() {
FriedRice* friedRice = FriedRice::FriedRiceBuilder(7).AddEgg().AddBeef().AddOnion().Build();
friedRice->showFlavors();
return 0;
}
golang实现
package builder
type Builder interface {
part1()
part2()
part3()
}
type Director struct {
builder Builder
}
func NewDirector(b Builder) *Director {
return &Director{builder: b}
}
func (d *Director) Constructor() {
d.builder.part1()
d.builder.part2()
d.builder.part3()
}
type FirstBuilder struct {
result string
}
func (fb *FirstBuilder) part1() {
fb.result += "1"
}
func (fb *FirstBuilder) part2() {
fb.result += "2"
}
func (fb *FirstBuilder) part3() {
fb.result += "3"
}
func (fb *FirstBuilder) Result() string {
return fb.result
}
type SecondBuilder struct {
result string
}
func (sb *SecondBuilder) part1() {
sb.result += "11"
}
func (sb *SecondBuilder) part2() {
sb.result += "22"
}
func (sb *SecondBuilder) part3() {
sb.result += "33"
}
func (sb *SecondBuilder) Result() string {
return sb.result
}
测试用例
package builder
import (
"reflect"
"testing"
)
func TestFirstBuilder_Result(t *testing.T) {
fb := FirstBuilder{}
d := NewDirector(&fb)
d.Constructor()
if reflect.ValueOf(d.builder).Interface().(*FirstBuilder).Result() != "123" {
t.Error("first builder test failed")
}
}
func TestSecondBuilder_Result(t *testing.T) {
sb := SecondBuilder{}
d := NewDirector(&sb)
d.Constructor()
if reflect.ValueOf(d.builder).Interface().(*SecondBuilder).Result() != "112233" {
t.Error("second builder test failed")
}
}
测试结果
go test -v .
=== RUN TestFirstBuilder_Result
--- PASS: TestFirstBuilder_Result (0.00s)
=== RUN TestSecondBuilder_Result
--- PASS: TestSecondBuilder_Result (0.00s)
PASS
ok design-patterns/builder 0.521s
优缺点
优点
- 松散耦合
生成器模式可以用同一个构建算法构建出表现上完全不同的产品,实现产品构建和产品表现上的分离。生成器模式正是把产品构建的过程独立出来,使它和具体产品的表现松散耦合,从而使得构建算法可以复用,而具体产品表现也可以灵活地、方便地扩展和切换。 - 可以很容易地改变产品的内部表示
在生成器模式中,由于FriedRiceBuilder对象只是提供接口给FriedRice使用,那么具体的部件创建和装配方式是被FriedRiceBuilder接口隐藏了的,FriedRice并不知道这些具体的实现细节。这样一来,要想改变产品的内部表示,只需要切换FriedRiceBuilder的具体实现即可,不用管FriedRice,因此变得很容易。 - 更好的复用性
生成器模式很好地实现了构建算法和具体产品实现的分离。这样一来,使得构建产品的算法可以复用。同样的道理,具体产品的实现也可以复用,同一个产品的实现,可以配合不同的构建算法使用。
缺点
- 生成器模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用生成器模式,因此其使用范围受到一定的限制。
- 如果产品的内部变化复杂,可能会导致需要定义很多具体生成器类来实现这种变化,导致系统变得很庞大。
no pains ,no gains.
给自己加油,为未来奋斗。