读书笔记-设计模式-可复用版-Builder 建造者模式

Builder 建造者模式是5种创建型模式中的一种,重复那句话,既然是创建型模式,他的目的就是用来“构建其它对象“的。

但Builder,通常是用于”构建“复杂的对象,并强制一步一步构建的过程,来生成复杂的对象。

概念:

将一个复杂的对象构建和它的表示分离。使得同样的构建过程,可以创建不同的表示。

结构图:

既然是创建型,就需要有,我们具体要创建的东西(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();

输出结果:

再想一个Builder构建者的例子,插花,花束是Product,包含了N种不同的花,

通过Builder可以获取所有可用的花,并提供了组合他们的方法

Director则是通过Builder提供的接口,一步一步的构建出最后的花束

放在现实中,花束是Product,Builder就是花店,提供了各种花,Director就是你自己或是说

插花人,由你来决定要构建成什么样子。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值