13.好菜每回味不同 - 建造者模式(Builder Pattern)

13. 1 炒面没放盐

  • 时间: 4月9日22点   地点: 小菜大鸟住所的客厅   人物: 小菜, 大鸟

     “小菜, 讲了半天, 肚子饿的厉害, 走, 吃宵夜去.” 大鸟摸着肚子说.
     “你请客!?”

     “我教了你这么多, 你也不打算报答一下, 还要我请客? 搞没搞错.”
     “啊, 说的也是, 这样吧, 我请客, 你买单, 嘻嘻!” 小菜傻笑道, “我身上没带钱”
     “你这个穷酸菜, 行了, 我来埋单吧.” 大鸟等不及了, 拿起外套就往外走.
     “等等我, 我把电脑关一下.”
     ……..

13.2 建造小人一

     “给你出个题目, 看看你能不能真正体会到流程的抽象. 我的要求是你用程序画一个小人, 这在游戏程序里非常常见, 现在简单一点, 要求是小人要有头, 身体, 两手, 两脚就可以了.”
     “这个程序不难, 我回去就写给你看看.”

  • 时间:4月9日23点   地点: 小菜大鸟住所的客厅   人物: 小菜, 大鸟

     “大鸟, 程序写出来了, 简单了点, 但功能实现了.”

1
2
3
4
5
6
7
// 实在是不想用 android 的 paint, 观其大略, 观其大略.
System.out.println("头");
System.out.println("瘦小人的身子");
System.out.println("左手");
System.out.println("右手");
System.out.println("左脚");
System.out.println("右脚");

     “写的很快, 那么我现在要你在画一个身体比较胖的小人呢.”
     “那不难啊, 我马上做好.”

1
2
3
4
5
6
// 观其大略.
System.out.println("头");
System.out.println("瘦小人的身子");
System.out.println("左手");
System.out.println("右手");
System.out.println("左脚");

     “啊, 等等, 我少画了一条腿.”

1
System.out.println("右脚");

     “哈, 这就和我们刚才去吃炒面一样, 老板忘记了放盐, 让本是非常美味的宵夜变得无趣. 如果是让你开发一个游戏程序, 里面健全的人物却少了一条腿, 那怎么能行呢?”
     “是呀, 画人的时候, 头身手脚是必不可少的, 不管什么人物, 开发时是不能少的.”
     “你现在的代码全部写在 Forms1.cs 窗体里, 我要是需要再别的地方用这些画小人的程序怎么办?”

13.2 建造小人二

     “嘿, 你的意思是分离, 这不难办, 我建两个类, 一个是瘦人的类, 一个是胖人的类, 不管谁都可以调用它了.”

1
2
3
4
5
6
7
8
9
10
11
12
public class PersonThinBuilder {
// 其他参数..
public void build() {
System.out.println("头");
System.out.println("瘦小人的身子");
System.out.println("左手");
System.out.println("右手");
System.out.println("左脚");
System.out.println("右脚");
}
}

     “胖人的类也是相似的. 然后我在客户端里面就只要这样写就可以了.”

1
2
3
4
5
6
7
public static void main(String[] args) {
PersonThinBuilder ptb = new PersonThinBuilder();
ptb.build();
PersonFatBuilder pfb = new PersonFatBuilder();
pfb.build();
}

     “你这样写的确达到了可以复用这两个小人程序的目的.” 大鸟说, “但炒面忘记放盐的问题依然没有解决. 比如我现在需要你加一个高个的小人, 你会不会因为编程不注意, 又让他缺胳膊少腿呢?”
     “是呀, 最好的办法是规定, 凡事建造小人, 都必须要有头和身体, 以及两手两脚.”

13.4 建造者模式

     “你仔细分析会发现, 这里建造小人的 ‘过程’ 是稳定的, 都需要头身手脚, 而具体建造的 ‘细节’ 是不同的, 有胖有瘦有高有矮. 但对于用户来讲, 我才不管这些, 我只想告诉你, 我需要一个胖小人来游戏, 于是你就建造一个给我就行了. 如果你需要将一个复杂的对象的构建与它的表示分离, 使得同样的构建过程可以创建不同的表示的意图时, 我们需要应用于一个设计模式, ‘建造者模式(Builder)‘, 又叫生成器模式. 建造者模式可以讲一个产品的内部表象与产品的生成过程分割开来, 从而可以使一个建造过程生成具有不同的内部表象的产品对象. 如果我们用了建造者模式, 那么用户就只需指定需要建造的类型就可以得到他们, 而具体建造的过程和细节就不需要知道了.

建造者模式(Builder), 将一个复杂对象的构建与它的表示分离, 使得同样的构建过程可以创建不同的表示.[DP]

     “那怎么使用建造者模式呢?”
     “一步一步来, 首先我们要画小人, 都需要画什么?”
     “头, 身体, 左手, 右手, 左脚, 右脚.”
     “对的, 所以我们先定义一个抽象的建造人的类, 来把这个过程给稳定住, 不让任何人遗忘当中的任何一步.”

1
2
3
4
5
6
7
8
9
public abstract class PersonBuilder {
// 其他参数方法...
public abstract void buildHead();
public abstract void buildBody();
public abstract void buildArmLeft();
public abstract void buildArmRight();
public abstract void buildLegLeft();
public abstract void buildLegRight();
}

     “然后, 我们需要建造一个瘦的小人, 则让这个瘦子类去继承这个抽象类, 那就必须去重写这些抽象的方法了. 否则编译器也不让你通过.”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class PersonThinBuilder extends PersonBuilder {
@Override
public void buildHead() { System.out.println("头"); }
@Override
public void buildBody() { System.out.println("瘦小人的身子"); }
@Override
public void buildArmLeft() { System.out.println("左手"); }
@Override
public void buildArmRight() { System.out.println("右手"); }
@Override
public void buildLegLeft() { System.out.println("左脚"); }
@Override
public void buildLegRight() { System.out.println("右脚"); }
}

     “当然, 胖人或高个子其实都是用类似的代码去实现这个类就可以了.”
     “这样子, 我在客户端调用时, 还是需要知道头身手脚这些方法呀? 没有解决问题.” 小菜不解的问.
     “别急, 我们还缺少建造者模式中一个很重要的类, 指挥者(Director), 用它来控制建造过程, 也用它来隔离用户与建造者的关联.”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class PersonDirector {
private PersonBuilder pb;
public PersonDirector(PersonBuilder pb) { // 用户告诉指挥者需要什么样的小人.
this.pb = pb;
}
public void createPerson() { // 根据用户的选择建造小人.
pb.buildHead();
pb.buildBody();
pb.buildArmLeft();
pb.buildArmRight();
pb.buildLegLeft();
pb.buildLegRight();
}
}

     “你看到没有, PersonDirector 类的目的就是根据用户的选择来一步一步建造小人, 而建造的过程在指挥者这里完成了, 用户就不需要知道了, 而且, 由于这个过程每一步都是要做的, 那就不会让少画了一只手, 少画了一条腿的问题出现了.”



     “哈, 我明白了, 客户端的代码让我来写吧, 应该也不难实现了.”

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
PersonThinBuilder ptb = new PersonThinBuilder();
PersonDirector pdThin = new PersonDirector(ptb);
pdThin.createPerson();
PersonFatBuilder pfb = new PersonFatBuilder();
PersonDirector pdFat = new PersonDirector(pfb);
pdFat.createPerson();
}

     “试想一下, 我如果需要增加一个高个子和矮个子的小人, 我们应该怎么做?”
     “加两个类, 一个高个子和矮个子的小人类, 让他们都去继承 PersonBuilder, 然后客户调用就可以了. 但我有个问题, 如果我需要细化一些, 比如人的五官, 手的上臂, 前臂和手掌, 大腿小腿这些, 如何办呢?”
     “问得好, 这就是需要权衡, 如果这些细节是每个具体的小人都需要构建的, 那就应该要加进去, 反之, 就没有必要. 其实建造者模式是逐步建造产品的, 所以建造者的 Builder 类里的那些健在方法必须要足够普遍, 以便为各种类型的具体建造者构造.”

13.5 建造者模式解析

     “来, 我们看看建造者模式的结构”



     “现在你看这张图就不会感觉陌生了. 来总结一下 builder是什么?
     “是一个建造小人各部分的抽象类.”
     “概括地说, 是为了创建一个 Product 对象的各个部件指定的抽象接口. ConcreteBuilder 是什么呢?
     “具体的小人建造者, 具体实现如何画出小人的头手身脚各个部分.”
     “对的, 他是具体的建造者, 实现 Builder 接口, 构造和装配各个部件. Product 当然就是那些具体的小人, 产品角色了, Director是什么
     “指挥者, 用来根据用户的需求构建小人对象.”
     “恩, 他是构建一个使用 Builder 接口的对象
     “那都是什么时候需要使用建造者模式呢?”
     “它主要用于创建爱你一些复杂变化的对象, 这些对象内部的构建间的建造顺序通常是稳定的, 但对象内部的构建通常面临这复杂的变化.
     “哦, 是不是建造者模式的好处就是是的构建代码与表示代码分离, 由于建造者隐藏了该产品是如何组装的, 所以若需要改变一个产品的内部表示, 只需要在定义一个具体的建造者模式就可以了.
     “来来来, 我们来试着把建造者模式的基本代码推演一下, 以便有一个更宏观的认识.”

13.6 建造者模式的基本代码

Product 产品类, 有多个部件构成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Product {
List<String> parts = new ArrayList<>();
// 添加产品部件
public void add(String parts) {
this.parts.add(parts);
}
// 列举所有的产品
public void show() {
System.out.println("\n 产品 创建---");
for (String part :
parts) {
System.out.println(part);
}
}
}

Builder 类 — 抽象类建造者, 确定产品由两个部件 PartA 和 PartB 组成, 并声明一个得到产品建造后结果的方法 — getResult.

1
2
3
4
5
public abstract class Builder {
public abstract void buildPartA();
public abstract void buildPartB();
public abstract Product getResult();
}

ConcreteBuilder1 类 — 具体建造这类.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ConcreteBuilder1 extends Builder {
private Product product = new Product();
// 具体建造者的两个部件是A和B
@Override
public void buildPartA() { product.add("部件A"); }
@Override
public void buildPartB() { product.add("部件B"); }
@Override
public Product getResult() { return product; }
}

ConcreteBuilder2 类 — 具体建造这类.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ConcreteBuilder2 extends Builder {
private Product product = new Product();
// 具体建造者的两个部件是X和Y
@Override
public void buildPartA() { product.add("部件X"); }
@Override
public void buildPartB() { product.add("部件Y"); }
@Override
public Product getResult() { return product; }
}

Director 类 — 指挥者类

1
2
3
4
5
6
public class Director {
public void construct(Builder builder) {
builder.buildPartA();
builder.buildPartB();
}
}

客户端代码, 客户不需要知道具体的建造过程

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
Director director = new Director();
Builder b1 = new ConcreteBuilder1();
Builder b2 = new ConcreteBuilder2();
director.construct(b1);
Product p1 = b1.getResult();
p1.show();
director.construct(b2);
Product p2 = b2.getResult();
p2.show();
}

     “所以说, 建造者模式是在当[创建复杂对象的算法]应该独立于[该对象的组成部分以及他们的装配方式]时使用的模式
     “如果今天大排档做的炒面的老板知道建造者模式, 他就明白, 盐是一定要放的, 不然, 就是编译不通过.”
     “什么呀, 不然, 钱就赚不到了, 而且还大大丧失我们对他厨艺的信任. 看来各行各业都需要懂模式呀.”

~感谢捧场,您的支持将鼓励我继续创作~