理解设计模式之原型模式、建造者模式

抽象工厂和工厂方法从外部封装对象的创建,解耦对象的直接引用关系,而单例模式、原型模式、建造者模式则是从内部创建对外可用的对象。
单例模式已经介绍过了,这里主要说说原型模式和建造者模式。

从内部构造对象

从内部控制实例个数,构造出一个单例,这是单例模式;
从内部控制数据的复制,可以构造出平行的对象,这是原型模式;
从内部控制对象的组装,可以构造出自定义对象,这是建造者模式;

为什么要从内部构造?
首先,在不需要的情况下,外部构造有两个问题:

  1. 增加了复杂度。只是创建一个对象,就是说不需要处理多个对象,完全不需要增加这种复杂度。
  2. 破坏了封装性。内部数据的处理,也必须要暴露给外部构造对象(比如说工厂);

原型模式

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

大家先看看这样的效果是不是原型模式想要的?

1
2
3
4
5
6
// 错误示例,特此标注
public class Prototype {
public Prototype clone() {
return this;
}
}

貌似是“复制”了和自己一模一样(真的是一模一样)的对象了,但是总感觉哪里不对,是不是太简单了?
感觉是对的,上述代码只是提供了一个引用,并没有创建新的对象,所以我们要解决对象的复制问题:得真的new一个对象出来。
当然完全可以自定义复制的细节,比如说改变一些值,复制出不一样的对象:

1
2
3
4
5
6
7
public class Prototype {
public Prototype clone() {
Prototype prototype = new Prototype();
// prototype.setXX()
return prototype;
}
}

这种不依赖于引用的复制称为深拷贝,因为对象中会引用对象,引用对象又会引用对象,“冤冤相报何时了”,这会造成真正的深拷贝是很难的,甚至有时是不可能的。
另外,在java中虽然在Object级别就定义了clone(),但是然并卵,如果没有实现Cloneable接口,调用clone就会异常,这个clone就是一定要你自己实现的。
我们也参考java实现原型模式的最终形态,借用一下Cloneable:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class User implements Cloneable {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public Object clone() throws CloneNotSupportedException {
User user = new User(name, age);
// user.name = "cloner name";
return user;
}
}

UML类图如下:
原型模式UML类图

建造者模式

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

建造者模式其实是简化了复杂对象的构建。什么样的复杂对象?
复杂到需要你一步一步去配置它,最终才能得到它的复杂对象,比如Dialog,XXConfig …,可以向导式带你构建一个Product。
首先,建立Product和Builder,Builder构建Product:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Product {
public void buildStepA() {
}
public void buildStepB() {
}
public void buildStepC() {
}
}
public class Builder {
private Product product = new Product();
public void buildStepA() {
product.buildStepA();
}
public void buildStepB() {
product.buildStepB();
}
public void buildStepC() {
product.buildStepC();
}
public Product create() {
return product;
}
}

可能有人要骂我了,什么玩意?No,no,no,这里包含了一个建造者模式重要的基础:分离对象的构建和它的表示。实现代码都在Product自身里,但是构建过程全部放在Builder里。
为什么要把构建和表示分离?说回去了,因为这个复杂对象,它的构建(配置)太复杂,职责分离,干脆把这个构建操作专门抽象到Builder里,简化产品本身。
其次,对Builder进行抽象,就能得到多种Builder实现,创造不同的表示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public interface IBuilder {
void buildStepA();
void buildStepB();
void buildStepC();
}
public class BuilderA implements IBuilder {
private Product product = new Product();
public void buildStepA() {
product.buildStepA();
}
// 互换B、C顺序
public void buildStepB() {
product.buildStepC();
}
public void buildStepC() {
product.buildStepB();
}
public Product create() {
return product;
}
}
public class BuilderB implements IBuilder {
private Product product = new Product();
@Override
public void buildStepA() {
}
@Override
public void buildStepB() {
}
@Override
public void buildStepC() {
}
public Product create() {
// 各种定制操作。比如检测、延迟...
// B、C二者只能选一,可以巧妙的在create里面做检测
// if (A && B) { throw new RuntimeException("A和B只能选择一个") };
return product;
}
}

既然这么复杂了,就再复杂一点,创建一个指导者Director,接受不同的Builder构建出不同的Product。
当然,也可以对产品抽象出一系列具体产品,但考虑到不同的产品它的构建基本不同,产品的抽象意义并不大。
事实上,就Director和Builder的抽象常常被简化,使用简单的一个Builder即可满足大部分要求。
UML类图如下:
建造者模式UML类图

小结

原型模式和建造者模式,从对象内部考虑对象的创建,更多的是偏向数据或者行为的处理,这和其他很多设计模式偏向关系的处理是有很大的不同的。