来源:https://blog.csdn.net/ayunlong/article/details/130298135

Java设计模式-原型模式(Prototype Pattern)

原型模式属于对象的创建模式。通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象。这就是选型模式的用意。

一、什么是原型模式

用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。原型实例指定了要创建对象的种类,创建时无需知道对象创建细节。

原型模式要求对象实现一个可以“克隆”自身的接口,这样就可以通过复制一个实例对象本身来创建一个新的实例。这样一来,通过原型实例创建新的对象,就不再需要关心这个实例本身的类型,只要实现了克隆自身的方法,就可以通过这个方法来获取新的对象,而无须再去通过new来创建。

这种形式涉及到三个角色:

  • 客户(Client)角色:客户类提出创建对象的请求。
  • 抽象原型(Prototype)角色:抽象角色,通常由一个Java接口或Java抽象类实现,此角色给出所有的具体原型类所需的接口(如Object的clone方法)。
  • 具体原型(Concrete Prototype)角色:被复制的对象,实现复制接口

二、原型模式的2种实现方式

自定义克隆方法

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
package org.PrototypePattern;

interface Sheep{
public DuoLiSheep clone();
}

class DuoLiSheep implements Sheep{
private String name;

public String getName() {
return name;
}

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

@Override
public DuoLiSheep clone() {
DuoLiSheep duoLiSheep = new DuoLiSheep();
duoLiSheep.setName(this.getName());
return duoLiSheep;
}
}


public class Custom {
public static void main(String[] args) {
// 创建一个对象实例
DuoLiSheep duoLiSheep = new DuoLiSheep();
duoLiSheep.setName("多利");

// 非克隆模式克隆对象
DuoLiSheep duoLiSheep1 = new DuoLiSheep();
// 如果对象内部很复杂,则需要非常多的构建细节,容易出错且繁琐
duoLiSheep1.setName(duoLiSheep.getName());

// 自定义原型模式克隆对象,无需关注如何实现,只要调用clone我就能获取一个新的对象
DuoLiSheep duoLiSheep2 = duoLiSheep.clone();

System.out.println("多利1号与多利2号是同一个对象吗? " + (duoLiSheep.equals(duoLiSheep2) ?"是":"不是"));
}
}

// 运行结果
多利1号与多利2号是同一个对象吗? 不是

上面的代码中写了普通克隆方案和原型模式的克隆方法,可以总结出

  1. 原型模式操作简单,屏蔽对象创建细节
  2. 普通的克隆方法需要获取原始对象的属性,一一赋值给新对象,过于复杂

Object类的clone方法

clone()方法将对象复制了一份并返还给调用者。所谓“复制”的含义与clone()方法是怎么实现的。一般而言,clone()方法满足以下的描述:

  • 对任何的对象x,都有:x.clone()!=x。换言之,克隆对象与原对象不是同一个对象。
  • 对任何的对象x,都有:x.clone().getClass() == x.getClass(),换言之,克隆对象与原对象的类型一样。
  • 如果对象x的equals()方法定义其恰当的话,那么x.clone().equals(x)应当成立的。
    在JAVA语言的API中,凡是提供了clone()方法的类,都满足上面的这些条件。JAVA语言的设计师在设计自己的clone()方法时,也应当遵守着三个条件。一般来说,上面的三个条件中的前两个是必需的,而第三个是可选的。
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
package org.PrototypePattern.version2;

class DuoLiSheep implements Cloneable{
private String name;

public String getName() {
return name;
}

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

@Override
public Object clone() throws CloneNotSupportedException {
System.out.println("多利羊复制成功!");
return (DuoLiSheep) super.clone();
}
}
public class JDKClone {
public static void main(String[] args) throws CloneNotSupportedException {
DuoLiSheep duoLiSheep = new DuoLiSheep();
duoLiSheep.setName("多利");
DuoLiSheep duoLiSheep1 = (DuoLiSheep) duoLiSheep.clone();
System.out.println("两个对象是否相同?" + (duoLiSheep1.equals(duoLiSheep)));

}
}
// 运行结果如下
多利羊复制成功!
两个对象是否相同?false

优点

  • Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。

  • 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。
    缺点

  • 需要为每一个类都配置一个clone方法

  • clone方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。

  • 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。

浅拷贝是什么?
数据类型是基本数据类型或String,浅拷贝直接进行值传递,即将属性值直接复制一份给新的对象。

数据类型是引用类型(对象、数组),浅拷贝会进行引用传递,也就是将成员变量的引用值(内存地址)复制一份给新的对象。如属性A是一个引用类型,那么浅拷贝后newObject.A=oldObject.A。如果A有修改,将影响到newObject和oldObject。

上面用原型模式改进的克隆羊的例子就是浅拷贝,浅拷贝是用默认的clone()来实现的

深拷贝是什么?
相对于浅拷贝来说,深拷贝对基本类型或String类型的处理是一样的,不同的是处理引用类型。深拷贝会为所有的引用类型的成员变量申请存储空间,并复制每个引用数据成员变量所引用的对象,直到改对象可达的所有对象。 深拷贝的两种实现方式

方式1:重写clone方法来实现深度克隆

方式2:利用序列化实现深度克隆

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package org.PrototypePattern.version4;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

class DuoLiSheep implements Cloneable, Serializable {
private String name;
private List child = new ArrayList();

public String getName() {
return name;
}

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

public List getChild() {
return child;
}

public void setChild(List child) {
this.child = child;
}

@Override
public Object clone() throws CloneNotSupportedException {
//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;

try {

// 序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
//当前这个对象以对象流的方式输出
oos.writeObject(this);

// 反序列化,bos存放了对象流,装饰模式中我们讲过其中原理
// ObjectOutputStream是对ByteArrayOutputStream增强装饰而已,流在bos中
// 序列化是对象转byte,反序列化是将byte转对象
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
// 读取数据流实现反序列化
DuoLiSheep copyObj = (DuoLiSheep) ois.readObject();

return copyObj;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
}
public class DeepCopy {
public static void main(String[] args) throws CloneNotSupportedException {
DuoLiSheep duoLiSheep = new DuoLiSheep();
duoLiSheep.setName("多利");
DuoLiSheep duoLiSheep1 = (DuoLiSheep) duoLiSheep.clone();
System.out.println("两个对象是否相同?" + (duoLiSheep1.equals(duoLiSheep)));
}
}
// 运行结果
两个对象是否相同?false

反序列化可以实现对象的深拷贝,其实就是创建了新的对象,在单例模式中我们提到过反序列化可以破坏单例模式,就是因为反序列化创建的是新的对象,而且属性与旧对象完全相同,无需区别对象的属性是简单类型还是负责类型。

  1. 原型模式的注意事项和细节
  2. 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
  3. 不用重新初始化对象,而是动态地获得对象运行时的状态
  4. 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码。
  5. 在实现深克隆的时候可能需要比较复杂的代码
  6. 缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了ocp原则。