既然是创建型,就需要有,我们具体要创建的东西(Product)产品,这些产品类通常比较复杂,比如有众多的组件(拥有众多的参数),而这些组件之间的组合是多式多样的
那么,如果你想要通过构造函数去创建,你就都需要提供很多种,而且需求变更更带来的修改成本......显然是不合理的,所以产品的创建,必须应该具有灵活性
Builder类是建造者模式的核心,里面包括了构建产品的所有接口(每一个环节)。但Builder通常并不生成最终的构建结果,最终的构建我们通常是放在Director(主管或导演)中
可以说是将构建过程和构建结果再次分离。
这里举一个例子:
我有一个怪物Monster的对象(Product),他可以包含头,眼睛,嘴,耳朵,手,脚等等很多部分,但怪物的设定,可以是很随意的组合,可以像人,也可以是四不像
比如我需要一个似人的怪物,一个长着三只眼睛两条腿的,一只眼睛一只胳膊一条腿的,两个头三张嘴四只脚的......你会发现,组合是多样化的
通过建立Builder类,我们将实现构建Monster的每一个环节(添加头,脚,眼睛,腿等),上面提到过,Builder只包含怪物的每一个环节,步骤,但最终构建成什么样子,这需要放在Director中构建。
Builder只提供构建产品每一个环节的接口。
简化的代码:
public class Monster{
public int hp;
public int maxHp;
....
public List<MonsterComponent> components = new List<MonsterComponent>();
}
现在我们的需求是构建这些不同风格的怪物,将构建的代码直接添加到Product中是可以的,但并不建议这样做,因为我构建部分的修改,不应该影响到Product本身。
所以,通常将构建的过程定义在一个新的类或是接口中,从而实现构建和表示分离。
另一点好处是,当需求发生了变更,可能要有更复杂的构建,我可能需要有多个类需去实现构建的接口,习惯性定义成接口,面对难以预料的需求变更,代码会更具有扩展性。
简化的代码:
public class IMonsterBuilder{
Monster create();
void AddHead(...);
void AddHand(...);
void AddEar(...);
void AddLeg(...");
.....
}
通过具体的Builder类(ConcreteBuilder),实现IMonsterBuilder接口,并实现相应的方法。(并不是每一个方法都需要去实现,这里就提高了更多的灵活性)
简化代码:
public class MonsterBuilder:IMonsterBuilder{
public Monster monster;
public Monster create()
{
return xxx;
}
public void AddHead(..)
{
......
}
public void AddHand(...)
{
.......
}
}
MonsterBuilder中包含了构建一个完整的复杂的对象的每一个环节,但通常他并不包含最终构建的结果。
因为同样,不同组合构建出来的结果,不应该影响到构建的环节。
换句话说,我随意的组合产品,这些环节(接口)都是MonsterBuilder提供的,但如果写在一起(耦合),那么,我可能只是去掉了结果中,这只怪物的胳膊或是增加两条腿的修改,但导致MonsterBuilder构建的类也被编译了,甚至可能会引起错误的修改,这都是不合理的设计
所以,具体不同样式的构建我就需要放在另外一个类中去实现,这就是最终的Director了。
现在,Product,Builder,Director的结构关系,应该可以明确了。
这样,无论我怎么调整构建结果,Builder构建类和Product产品,都不会受到影响。
所以在设计中,一定要考虑,这些接口为什么要分开,是否可以耦合在一起?
Builder可以定义为接口,也可以定义为Class,下面是书中定义成class的形式:
class MazeBuilder{
public:
virtual void BuildMaze();
virtual void BuildRoom(int room){}
virtual void BuildDoor(int ,roomFrom,int roomTo){}
virtual Maze* GetMaze() {return 0;}
protected:
MazeBuilder();
}
可以看到,MazeBuilder并没有将所有的接口,定义为纯虚函数,这是为了便于派生类可以只重新定义它所感兴趣的接口,而不必像接口那样,要重新定义所有的接口。
所以,我个人也是更偏向于定义成抽象类,而不是接口的形式。
有了具体实现MazeBuilder的派生类,在Director中,就可以去一步一步的去构建复杂的对象了。
沿用上面的例子。在Director中可以定义如下方法来创建最终的复杂对象。
这里Director被定义为MazeGame
Maze* MazeGame::CreateMaze(MazeBuilder& builder){
builder.BuildMaze();//创建Maze对象,即Product
builder.BuildRoom(1);
builder.BuildRoom(2);
builder.BuildDoor(1,2);
return builder.GetMaze();
}
可以发现,每一个环节都被builder隐藏了内部的实现细节。
函数接受MazeBuilder& 引用,这就意味着,我可以重用MazeBuilder来创建更多不同组合的复杂对象。
比如:
Maze* MazeGame::CreateComplexMaze(MazeBuilder& builder)
{
builder.BuildRoom(1);
//...
builder.BuildRoom(1001);
return builder.GetMaze();
}
再次重复,MazeBuilder,Builder并不创建最终的结果,只提供创建结果的每一个必要的环节。而且通常环节的部分,也是由派生类来实现的,这样会更具有扩展性。
public class StandardMazeBuilder:MazeBuilder{
public:
StandardMazeBuilde();
virtual void BuildMaze();
virtual void BuildRoom(int);
virtual void BuildDoor(int,int);
virutal Maze* GetMaze();
private:
//定义具体的产品类,StandardMazeBuilder包含了具体产品类所有的创建环节
Maze* _currentMaze;
}
public class ClassicMazeBuilder:MazeBuilder{
//...
}
public class OldSchoolMazeBuilder:MazeBuilder{
//...
}
在StandardMazeBuilder中定义了_currentMaze对象,即是我们的Product,
通过BuildMaze()函数,完成_currentMaze的new初始化操作,并通过GetMaze函数,返回给调用者(Director)
最后看下时序图:
ConcreteBuilder aConcreteBuilder= new ConcreteBuilder();
Director aDirector = new Director(aConcrteBuilder);
aDirector.Construct();
//aDirector.ConstructB();
//aDirector.ConstructC();
//......
ProductXXX product = aDirector.GetResult();
创建ConcreteBuilder,具体的Builder对象,并做为参数,传给Director,Director可以利用Builder对象提供的接口,去一步一步的构建出复杂的对象(最终的结果)
如果让我说一个Builder最常见,最典型应用的地方,我会脱口而出:弹出式对话框
接触过Android开发都看到如下这样的代码:
典型的Builder建造者模式应用,之前看到有的文章说,Android这种方式省去了Director,其实还是没理解Builder概念上的意图,如果你需要创建多个复杂的对象,当然还是需要Director来负责生成多个不同需求的复杂对象。
上面的代码好在Builder接口中默认均返回Builder本身,这样我构建的时候,可以直接通过点号调用其它的接口,书写起来很方便,很灵活。
我个人也是很建议写成这种形式。
而且我们可以看到上面的Builder是一个内部类,AlertDialog.Builder,好处是可以明确Builder的来源,不需要想着应该定义成什么名字,内部类,直接就定义叫Builder就好了。
另外,如果查看源码,发现在AlertDialog.Builder中,定义的并不是AlertDialog对象,而是AlertParams,将对话框所需要的参数也定义成class的形式,create时,再将AlertParams应用到AlertDialog中,也是很不错的设计。
以前写过一篇关于建造者模式的应用
https://www.jianshu.com/p/7dec2f4f3367
这次再复习,对建造者模式有了更多的认识,本来想重新实现一下厨房设计的案例,既然上面提到过创建不同的怪物,和厨房设计的结构是一样的,就实现一下应用建造模式来创建不同种类的怪物
例子比较简单,能够表达意思即可。
public class MonsterComponent
{
//...
}
public class HeadComponent : MonsterComponent
{
//...
}
public class EyeComponent : MonsterComponent
{
//...
}
public class MouthComponent : MonsterComponent
{
//...
}
public class HandComponent : MonsterComponent
{
//...
}
public class LegComponent : MonsterComponent
{
//...
}
public class Monster
{
public string name;
public List<MonsterComponent> components = new List<MonsterComponent>();
public Monster(string name)
{
this.name = name;
}
public void AddComponent(MonsterComponent component)
{
components.Add(component);
}
public void print()
{
int headcount = 0;
int eyecount = 0;
int mouthcount = 0;
int handcount = 0;
int legcount = 0;
GetComponentCount(out headcount, out eyecount, out mouthcount, out handcount, out legcount);
Debug.Log(string.Format("怪物名字:{0} 拥有{1} 个头 {2} 只眼睛 {3} 张嘴 {4} 只手 {5} 条腿",
name,headcount, eyecount, mouthcount, handcount, legcount));
}
private void GetComponentCount(out int headcount, out int eyecount,
out int mouthcount, out int handcount, out int legcount)
{
headcount = eyecount = mouthcount = handcount = legcount = 0;
for (int i = 0; i < components.Count; ++i)
{
MonsterComponent com = components[i];
if (com.ToString().StartsWith("Head"))
{
headcount++;
}
if (com.ToString().StartsWith("Eye"))
{
eyecount++;
}
if (com.ToString().StartsWith("Mouth"))
{
mouthcount++;
}
if (com.ToString().StartsWith("Hand"))
{
handcount++;
}
if (com.ToString().StartsWith("Leg"))
{
legcount++;
}
}
}
}
public class MonsterBaseBuilder
{
public virtual Monster create() { return null; }
public virtual MonsterBaseBuilder BuildMonster(string name) { return null; }
public virtual MonsterBaseBuilder AddHead() { return null; }
public virtual MonsterBaseBuilder AddEye() { return null; }
public virtual MonsterBaseBuilder AddMouth() { return null; }
public virtual MonsterBaseBuilder AddHand() { return null; }
public virtual MonsterBaseBuilder AddLeg() { return null; }
}
public class MonsterBuilder : MonsterBaseBuilder
{
private Monster monster;
public override Monster create() { return monster; }
public override MonsterBaseBuilder BuildMonster(string name)
{
monster = new Monster(name);
return this;
}
public override MonsterBaseBuilder AddHead()
{
monster.AddComponent(new HeadComponent());
return this;
}
public override MonsterBaseBuilder AddEye()
{
monster.AddComponent(new EyeComponent());
return this;
}
public override MonsterBaseBuilder AddMouth()
{
monster.AddComponent(new MouthComponent());
return this;
}
public override MonsterBaseBuilder AddHand()
{
monster.AddComponent(new HandComponent());
return this;
}
public override MonsterBaseBuilder AddLeg()
{
monster.AddComponent(new LegComponent());
return this;
}
}
public class MosnterDirector
{
private MonsterBaseBuilder builder;
public MosnterDirector(MonsterBaseBuilder builder)
{
this.builder = builder;
}
public Monster CreateMonster_1()
{
return builder.BuildMonster("monster1")
.AddHead()
.AddEye()
.AddMouth()
.AddLeg()
.AddLeg()
.AddLeg()
.AddLeg()
.AddLeg()
.create();
}
public Monster CreateMonster_2()
{
return builder.BuildMonster("monster2")
.AddHead()
.AddHead()
.AddEye()
.AddEye()
.AddMouth()
.AddHand()
.AddHand()
.AddLeg()
.AddLeg().
create();
}
public Monster CreateMonster_3()
{
return builder.BuildMonster("monster3")
.AddHead()
.AddHead()
.AddHead()
.AddEye()
.AddEye()
.AddEye()
.AddMouth()
.AddMouth()
.AddMouth()
.AddHand()
.AddHand()
.AddLeg()
.AddLeg()
.create();
}
}
CreateMonster_1,CreateMonster_2,CreateMonster_3方法中一长串的代码可以进行重构优化,但这里为了体现一步一步创建的过程。
Monster:Product,产品类,我们需要创建不同的Monster
MonsterComponent: 敌人组件,基类,由各个组件(部位)派生实现
HeadComponent
EyeComponent
MouthComponent
.....
均是具体部位的派生类
MonsterBaseBuilder:Builder的基本类,派生实现,实现创建Monster的每一个环节,构建的过程。
MonsterBuilder:派生自MonsterBaseBuilder,完成每一个环节的实现,这里采用了Android中AlertDialog的方式,每个函数返回this,方便书写,灵活使用。
MosnterDirector:导向器,用于创建构建最终的对象,接受MonsterBaseBuilder对象,复用它并进行不同需求的构建。
提供了三个方法:
CreateMonster_1();
CreateMonster_2();
CreateMonster_3();
测试代码:
MonsterBuilder builder = new MonsterBuilder();
MosnterDirector monsterDirector = new MosnterDirector(builder);
Monster monster1 = monsterDirector.CreateMonster_1();
Monster monster2 = monsterDirector.CreateMonster_2();
Monster monster3 = monsterDirector.CreateMonster_3();
monster1.print();
monster2.print();
monster3.print();
输出结果: