架构师内功心法,只是单纯听说过的原型模式详解

一、原型模式的应用场景

你一定遇到过这样的代码场景,有大量的getter、setter赋值的场景。例如这样的代码片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private SafetyStockMessage createKafkaMessage(SafetyStock safetyStock, HttpServletRequest request) {
SafetyStockMessage safetyStockMessage = new SafetyStockMessage();
safetyStockMessage.setId(safetyStock.getId());
safetyStockMessage.setProvinceCode(safetyStock.getProvinceCode());
safetyStockMessage.setRequestId(CodeConstants.REQUEST_ID);
safetyStockMessage.setRequestIp(CodeConstants.REQUEST_IP);
safetyStockMessage.setSerial(IdMakerUtil.make32Id());
safetyStockMessage.setStockMax(safetyStock.getStockMax());
safetyStockMessage.setStockMin(safetyStock.getStockMin());
safetyStockMessage.setProvince(safetyStock.getProvince());
safetyStockMessage.setCategoryName(safetyStock.getCategoryName());
safetyStockMessage.setUpdateTime(new Date());
safetyStockMessage.setUpdateBy(getLoginUser(request));
return safetyStockMessage;
}

代码看起来非常工整,命名也很规范,大家觉得这样的代码优雅吗?这样的代码属于纯体力劳动。如果使用原型模式,可以帮助我们解决这样的问题。

原型模式(Prototype Pattern)是指原型实例指定创建对象的种类,并且通过拷贝这样原型创建新的对象。

原型模式主要适用于以下场景:

1、类初始化消耗的资源较多;

2、new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等);

3、构造函数比较复杂;

4、循环体中生产大量对象。

原型模型的类结构图:

二、简单克隆

一个标准的原型模式代码,应该是这样的设计的。先创建原型Prototype接口:

1
2
3
public interface Prototype {
Prototype clone();
}

创建具体需要克隆的对象ConcretePrototype:

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
public class ConcretePrototype implements Prototype {

private String name;
private int age;
private List<String> hobbies;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public List<String> getHobbies() {
return hobbies;
}

public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}

@Override
public ConcretePrototype clone() {
ConcretePrototype concretePrototype = new ConcretePrototype();
concretePrototype.setName(this.name);
concretePrototype.setAge(this.age);
concretePrototype.setHobbies(this.hobbies);
return concretePrototype;
}
}

创建Client对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Client {

private Prototype prototype;

public Client(Prototype prototype) {
this.prototype = prototype;
}

public Prototype startClone(Prototype concretePrototype) {
return (Prototype)concretePrototype.clone();
}

}

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void main(String[] args) {
//创建一个具体的需要克隆的对象
ConcretePrototype concretePrototype = new ConcretePrototype();
//填充属性,准备测试
concretePrototype.setName("Kevin");
concretePrototype.setAge(18);
List<String> hobbies = new ArrayList<>();
concretePrototype.setHobbies(hobbies);
System.out.println(concretePrototype);

//创建Client对象,准备进行克隆
Client client = new Client(concretePrototype);
ConcretePrototype concretePrototypeClone = (ConcretePrototype)
client.startClone(concretePrototype);
System.out.println(concretePrototypeClone);

System.out.println("克隆对象中的引用类型地址值是:" +
concretePrototypeClone.getHobbies());
System.out.println("原对象中的引用类型地址值是:" +
concretePrototype.getHobbies());
System.out.println("对象地址比较:" + (concretePrototypeClone.getHobbies() ==
concretePrototype.getHobbies()));
}

运行结果:

从测试结果看出hobbies的引用地址是相同的,意味着不是复制值,而复制的是引用的地址。如果我们修改任何一个对象的属性值,concretePrototype和concretePrototypeClone的hobbies的值都会改变。这就是我们常说的浅克隆。只是完整复制了值类型数据,没有复制引用对象。换言之,所有的引用对象还是指向原来的对象,显然不是我们想要的结果。

下面我们继续改造代码,使用深度克隆。

三、深度克隆

我们来换一个场景,大家都知道齐天大圣孙悟空。首先它是一只猴子,有着七十二般变化,把一根毫毛放在嘴里一吹就变出千万个泼猴,手里还拿着金箍棒,金箍棒可变大变小。这就是我们耳熟能详的原型模式的经典体现。

创建原型猴子 Monkey 类:

1
2
3
4
5
6
7
public class Monkey {

public int height;
public int weight;
public Date birthday;

}

创建引用对象金箍棒GoldenCudgel类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class GoldenCudgel implements Serializable {

public float h = 100f;

public float d = 10f;

public void changeBig() {
this.d *= 2;
this.h *= 2;
}

public void changeSmall() {
this.d /= 2;
this.h /= 2;
}

}

创建具体的对象齐天大圣孙悟空MonkeyKing类:

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
47
48
49
50
51
52
53
54
55
public class MonkeyKing extends Monkey implements Cloneable, Serializable {

public GoldenCudgel goldenCudgel;

public MonkeyKing() {
this.birthday = new Date();
this.goldenCudgel = new GoldenCudgel();
}

@Override
protected Object clone() {
return this.deepClone();
}

/**
* 深克隆
* @return
*/
protected Object deepClone() {

try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);

ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);

MonkeyKing copy = (MonkeyKing) ois.readObject();
copy.birthday = new Date();
return copy;

} catch (IOException e) {
e.printStackTrace();
return null;
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}

/**
* 浅克隆
*/
public MonkeyKing shallowClone(MonkeyKing target) {
MonkeyKing monkeyKing = new MonkeyKing();
monkeyKing.height = target.height;
monkeyKing.weight = target.weight;

monkeyKing.goldenCudgel = target.goldenCudgel;
monkeyKing.birthday = new Date();

return monkeyKing;
}
}

测试代码:

1
2
3
4
5
6
7
8
9
10
 public static void main(String[] args) throws Exception {
MonkeyKing monkeyKing = new MonkeyKing();

MonkeyKing clone = (MonkeyKing)monkeyKing.clone();
System.out.println("深克隆:" + (monkeyKing.goldenCudgel == clone.goldenCudgel));

MonkeyKing shallow = new MonkeyKing();
MonkeyKing newMonkeyKing = shallow.shallowClone(shallow);
System.out.println("浅克隆" + (shallow.goldenCudgel = newMonkeyKing.goldenCudgel));
}

运行结果:

  • 克隆破坏单例模式

如果我们克隆的目标是单例模式创建的对象,那么意味着深克隆会破坏单例模式。如何防止克隆破坏单例,禁止深克隆便可。我们在单例的类中不实现Cloneable接口,在重写clone()方法中返回单例对象即可,代码如下:

1
2
3
4
@Override
protected Object clone() throws CloneNotSupportedException {
return INSTANCE;
}
  • Cloneable 源码分析

先看我们常用的 ArrayList 就实现了 Cloneable 接口,来看代码clone()方法的实现:

1
2
3
4
5
6
7
8
9
10
11
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}