原型模式

介绍

创建型模式,原型二字表明了该模式应该有一个样板实例,用户从这个样板对象中复制出一个内部属性一样的对象,这个过程也就是我们俗称的克隆。被复制的实例,就是我们所称的原型,这个原型是可定制的。原型模式多用于创建复杂的或者耗时的实例,因为这种情况下,复制一个已经存在的实例可以使程序的运行更加的高效。

使用场景

  • 类初始化的时候需要消耗非常多的资源,这个资源包括数据,硬件资源等,通过原型拷贝避免了这些消耗。
  • 通过new产生一个对象需要非常繁琐的数据准备或访问权限,这时候可以使用原型模式。
  • 一个对象需要提供给其他的对象访问,并且各个调用者可能都需要修改其值得时候可以考虑用原型模式拷贝多个对象供调用者使用,即保护性拷贝

需要注意的是,通过实现Cloneable接口的原型模式在调用clone函数构造实例的时候并不一定比通过new操作速度快,只有当通过new构造对象较为耗时时或者说成本较高的时候,通过clone方法才能够失效率得到提升。因此在使用Cloneable的时候需要考虑构建对象的成本以及做一些效率上的测试。当然原型模式也不一定非要实现Cloneable接口,也有其他的实现方式。

简单实现

文档拷贝

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
public class WordDocument implements Cloneable {

private String text;
private List<String> images = new ArrayList<>();

public WordDocument() {
System.out.println("文档的构造函数");
}


public String getText() {
return text;
}

public void setText(String text) {
this.text = text;
}

public List<String> getImages() {
return images;
}

public void addImages(String image) {
images.add(image);
}

@Override
protected Object clone(){


try {
WordDocument wordDocument = (WordDocument) super.clone();
wordDocument.text=this.text;
wordDocument.images=this.images;
return wordDocument;
} catch (Exception e) {
e.printStackTrace();
}
return null;


}

public void showDocument(){

System.out.println("----start----");

System.out.println("text"+text);
System.out.println("images");
for(String str:images){
System.out.println(str);
}

System.out.println("----end----");

}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args){

WordDocument originDc=new WordDocument();

originDc.setText("这是一篇文档");

originDc.addImages("图片一");
originDc.addImages("图片二");
originDc.addImages("图片三");

originDc.showDocument();

WordDocument doc2 = (WordDocument) originDc.clone();
doc2.showDocument();

doc2.setText("这是修改过的文档");
doc2.showDocument();

originDc.showDocument();
}

打印内容:

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
文档的构造函数
----start----
text这是一篇文档
images
图片一
图片二
图片三
----end----
----start----
text这是一篇文档
images
图片一
图片二
图片三
----end----
----start----
text这是修改过的文档
images
图片一
图片二
图片三
----end----
----start----
text这是一篇文档
images
图片一
图片二
图片三
----end----

浅拷贝跟深拷贝

上述原型模式的实现只是一个浅拷贝,也称为影子拷贝,这份拷贝实际上并不是将原始的所有字段都重新的构造了一份,而是副本文档的字段引用原始文档的字段。

我们知道,当A引用B就是说两个对象指向了同一个地址,当修改A的时候B也会改变,B修改时A同样会改变。

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){

WordDocument originDc=new WordDocument();

originDc.setText("这是一篇文档");

originDc.addImages("图片一");
originDc.addImages("图片二");
originDc.addImages("图片三");

originDc.showDocument();

WordDocument doc2 = (WordDocument) originDc.clone();
doc2.showDocument();

doc2.setText("这是修改过的文档");
doc2.addImages("哈哈.jpg");
doc2.showDocument();

originDc.showDocument();


}

打印结果

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
文档的构造函数
----start----
text这是一篇文档
images
图片一
图片二
图片三
----end----
----start----
text这是一篇文档
images
图片一
图片二
图片三
----end----
----start----
text这是修改过的文档
images
图片一
图片二
图片三
哈哈.jpg
----end----
----start----
text这是一篇文档
images
图片一
图片二
图片三
哈哈.jpg
----end----

因为是浅拷贝,所以doc2的images只是单纯的指向了this.images引用,并没有重新构造一个images对象,所以往doc2里面添加了图片,其实就是往this.images里面添加了图片。解决这个问题的话,可以用深拷贝,即在拷贝字段的时候,对于引用型的字段也要采用拷贝的形式,而不是单纯的引用的形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
protected WordDocument clone(){


try {
WordDocument wordDocument = (WordDocument) super.clone();
wordDocument.text=this.text;
wordDocument.images=(ArrayList<String>)this.images.clone();
return wordDocument;
} catch (Exception e) {
e.printStackTrace();
}
return null;


}

将wordDocument.images指向this.images的一份拷贝,而不是this.images本身,这样在doc2添加图片的时候并不会影响originDoc.

运行效果:

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
文档的构造函数
----start----
text这是一篇文档
images
图片一
图片二
图片三
----end----
----start----
text这是一篇文档
images
图片一
图片二
图片三
----end----
----start----
text这是修改过的文档
images
图片一
图片二
图片三
哈哈.jpg
----end----
----start----
text这是一篇文档
images
图片一
图片二
图片三
----end----

原型模式是一个非常简单的模式,他的核心问题是对原始对象进行拷贝,在这个模式使用的过程中要注意一点就是:深浅拷贝的问题。在开发的过程中,为了减少错误,建议大家在使用的时候尽量使用深拷贝,避免操作副本的时候影响原始对象的使用的问题。

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器