原型模式

一、简介

主要是用于创建重复的对象,同时又能保证性能。依旧是创建型模式。

比如你想要复制一个飞机,于是你模仿飞机做了一个壳子,但是这个飞机是飞不起来的,因为飞机内部的细节的元器件你是看不到的。

我们复制类的时候也是这样,当一个类里的成员是私有时,或者当一个类以接口的形式传过来时,我们只知道它是某个接口的实现类,并不知道具体是哪个类。当出现上述情况时,就无法复制。

这个是原型模式的类图

1.png

可以看到首先定义了一个prototype的接口,对于其他类,如果有克隆自己的需求,那么就会去实现这个接口。客服端通过调用克隆方法获得大量的克隆体。

二、代码实现

首先构造一个prototype接口

1
2
3
4
5
package Prototype;

public interface Prototype {
Object clone();
}

然后写一个Plane类并实现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
package Prototype;

public class Plane implements Prototype {
private String name;
private String type;

public Plane(){
name = "name" +Math.random();
type = "type" +Math.random();
}

public Plane(Plane plane){
this.name = plane.name;
this.type = plane.type;
}

public String getName() {
return name;
}

public String getType() {
return type;
}

@Override
public Object clone() {
return new Plane(this);
}
}

在main里尝试调用

1
2
3
4
5
6
7
8
9
10
package Prototype;

public class Main {
public static void main(String[] args) {
Plane plane = new Plane();
System.out.println(plane.getName()+" "+plane.getType());
Plane clone = (Plane) plane.clone();
System.out.println(clone.getName()+" "+clone.getType());
}
}

最后运行的结果属性值的确相同

Untitled

三、浅拷贝和深拷贝

但是当我稍微修改一下代码,在Plane类里添加一个数组a

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

public class Plane implements Prototype {
private String name;
private String type;
private int a[];

public Plane(){
name = "name" +Math.random();
type = "type" +Math.random();
a = new int[]{1, 2, 3, 4, 5};
}

public Plane(Plane plane){
this.name = plane.name;
this.type = plane.type;
this.a = plane.a;
}

public String getName() {
return name;
}

public String getType() {
return type;
}

public int getA0() {
return a[0];
}

public void setType(String tmp) {
type = tmp;
}

public void setA0(int tmp) {
a[0] = tmp;
}

@Override
public Object clone() {
return new Plane(this);
}
}

再次运行修改后的main代码

1
2
3
4
5
6
7
8
9
10
public class Main {
public static void main(String[] args) {
Plane plane = new Plane();
System.out.println(plane.getName()+" "+plane.getType()+" "+plane.getA0());
Plane clone = (Plane) plane.clone();
clone.setA0(100);
System.out.println(plane.getName()+" "+plane.getType()+" "+plane.getA0());
System.out.println(clone.getName()+" "+clone.getType()+" "+plane.getA0());
}
}

结果是这样的

Untitled

很明显上述代码的clone()是一个浅拷贝

事实上常见的原型模式方法,即重写Cloneable接口里的clone()方法,也是一种浅拷贝

1
2
3
4
5
6
7
8
9
public Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}

那么如何实现深克隆呢?

方法一:重写clone()方法,依次调用成员变量的clone方法,或者是手动开辟一个新的对象(比如下述代码就是建了一个新数组并复制元素)

1
2
3
4
5
6
7
public Plane(Plane plane){
this.name = plane.name;
this.type = plane.type;
// 创建一个新的数组并复制元素
this.a = new int[plane.a.length];
System.arraycopy(plane.a, 0, this.a, 0, plane.a.length);
}

补充一下System.arraycopy的参数

1
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
  • src:源数组,即要复制数据的数组。
  • srcPos:源数组中要开始复制的位置的索引。
  • dest:目标数组,即要将数据复制到的数组。
  • destPos:目标数组中开始复制的位置的索引。
  • length:要复制的数组元素的数量。

方法二:使用序列化和反序列化的方法(略)