java常用设计模式之原型模式及深浅拷贝

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

Prototype

Prototype类需要具备以下两个条件:
1、实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
2、重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,Prototype类需要将clone方法的作用域修改为public类型。

原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式。在实际应用中,原型模式很少单独出现。经常与其他模式混用,他的原型类Prototype也常用抽象类来替代。

1、原型模式-深浅拷贝

浅拷贝:只复制一个对象,对象内部存在的指向其他对象数组或者引用则不复制。
深拷贝:对象,对象内部的引用均复制。

Object类的clone方法只会拷贝对象中的基本的数据类型,对于数组、容器对象、引用对象等都不会拷贝,这就是浅拷贝。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝

原型模式-深浅拷贝实现

浅拷贝:
调用 java.lang.Object的clone()方法

深拷贝:
1、对象内部所有引用型对象都进行clone。
2、对象序列化

1.1、浅拷贝

先来看下浅拷贝的代码:

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
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;

class Prototype implements Cloneable {

private static final long serialVersionUID = -1251595400978173322L;
private ArrayList<String> list = new ArrayList<>();
private int mInt;

public ArrayList<String> getList() {
return list;
}

public void setList(ArrayList<String> list) {
this.list = list;
}

public int getmInt() {
return mInt;
}

public void setmInt(int mInt) {
this.mInt = mInt;
}

public Prototype clone() {
Prototype prototype = null;
try {
prototype = (Prototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return prototype;
}

}

Client类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.ArrayList;

public class Client {

public static void main(String[] args) {
Prototype cp = new Prototype();
cp.setmInt(10);
ArrayList list = new ArrayList<>();
list.add("原型模式");
cp.setList(list);
Prototype clonecp = cp.clone();
System.out.println("mInt:"+clonecp.getmInt());
System.out.println("list:"+clonecp.getList());
cp.setmInt(20);
list.add("clone后的原型模式");
cp.setList(list);
System.out.println("mInt:"+clonecp.getmInt());
System.out.println("list:"+clonecp.getList());
}

}

运行结果:
Prototype

可以看出,浅拷贝后,原型对象中的引用类型值改变,拷贝的对象引用类型的值也跟着变化,实际上浅拷贝的对象内部引用类型对象和原型对象内部的引用类型对象指向同一引用。clone这种对象的时候需要注意,如果希望拷贝的对象不受原型对象的影响,浅拷贝就不适用了。

1.2、深拷贝

接下来看深拷贝,深拷贝有2种实现方式:

1、对象中的每一个引用对象都进行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
32
33
34
35
import java.util.ArrayList;

class Prototype implements Cloneable {
private static final long serialVersionUID = -1251595400978173322L;
private ArrayList<String> list = new ArrayList<>();
private int mInt;
public ArrayList<String> getList() {
return list;
}

public void setList(ArrayList<String> list) {
this.list = list;
}

public int getmInt() {
return mInt;
}

public void setmInt(int mInt) {
this.mInt = mInt;
}

public Prototype clone() {
Prototype prototype = null;
try {
prototype = (Prototype) super.clone();
// deep clone
prototype.list = (ArrayList) this.list.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return prototype;
}

}

还是用之前的Client类,运行结果如下:

Prototype

此时 ,clone后,即使原型对象中list的值改变,也不会影响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
import java.io.*;
import java.util.ArrayList;

class Prototype implements Cloneable, Serializable {
private static final long serialVersionUID = -1251595400978173322L;
private ArrayList<String> list = new ArrayList<>();
private int mInt;
public ArrayList<String> getList() {
return list;
}

public void setList(ArrayList<String> list) {
this.list = list;
}

public int getmInt() {
return mInt;
}

public void setmInt(int mInt) {
this.mInt = mInt;
}

/**
* 深拷贝
* 使用序列化和反序列化实现深复制
* @return
*/
public Prototype deepClone() {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
byte[] bytes = bos.toByteArray();
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
// 克隆好的对象
return (Prototype) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}

Client类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.ArrayList;

public class Client {

public static void main(String[] args) {
Prototype cp = new Prototype();
cp.setmInt(10);
ArrayList list = new ArrayList<>();
list.add("原型模式");
cp.setList(list);
Prototype clonecp = cp.deepClone();
System.out.println("mInt:"+clonecp.getmInt());
System.out.println("list:"+clonecp.getList());
cp.setmInt(20);
list.add("clone后的原型模式");
cp.setList(list);
System.out.println("mInt:"+clonecp.getmInt());
System.out.println("list:"+clonecp.getList());
}

}

运行结果:
Prototype

可以看出,运行结果和第一种方式一样,实现了深拷贝。

使用原型模式创建对象比直接new一个对象在性能上要好的多,因为Object类的clone方法是一个本地方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。另一方面,克隆逃避构造函数的约束。


java常用设计模式之原型模式及深浅拷贝
https://river106.cn/posts/2c129b9b.html
作者
river106
发布于
2018年9月16日
许可协议