原型模式

使程序运行更高效-原型模式

原型模式多用于创建复杂的或者构造耗时的实例,因为这种情况下,复制一个已经存在的实例可使程序运行更高效。

原型模式的定义

用原型实例指定创建对象的种类,并通过复制这些原型创建新的对象。

使用场景

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

注意:实现Cloneable接口的原型模式在调用clone函数构造实例并不一定比new对象快。只有在new对象时候非常耗时或者繁琐的时候,通过clone函数才会获得效率上的提升。

简单实现

文档类 JavaBean

 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
package Prototype;

import java.util.ArrayList;

public class WordDocument implements Cloneable{
    //文本
    private String mText;
    //图片名列表
    private ArrayList<String> mImages=new ArrayList<String>();

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

    @Override
    protected WordDocument clone(){
        try {
            WordDocument document=(WordDocument) super.clone();
            document.mImages=this.mImages;
            document.mText=this.mText;
            return document;
        }catch (Exception e){
        }
        return null;
    }

    public String getmText() {
        return mText;
    }

    public void setmText(String mText) {
        this.mText = mText;
    }

    public ArrayList<String> getmImages() {
        return mImages;
    }

    public void setmImages(ArrayList<String> mImages) {
        this.mImages = mImages;
    }
    public void addImg(String name){
        this.mImages.add(name);
    }
    //打印文档内容
    public void showDocument(){
        System.out.println("-------word Content Start------");
        System.out.println("text:"+mText);
        System.out.println("imgList:");
        for (String name:mImages) {
            System.out.println("imgName:"+name);
        }
        System.out.println("---------word Content End-------");
    }
}

演示端

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package Prototype;

public class Client {
    public static void main(String[] args) {
        WordDocument document=new WordDocument();
        document.setmText("这个是文档");
        document.addImg("图片1");
        document.addImg("图片2");
        document.addImg("图片3");
        document.showDocument();

        //以原始文档为原型,拷贝一份副本
        WordDocument doc2= document.clone();
        doc2.showDocument();
        doc2.setmText("这个是修改过的副本文档");
        doc2.showDocument();

        document.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
-----------构造函数-----------
-------word Content Start------
text:这个是文档
imgList:
imgName:图片1
imgName:图片2
imgName:图片3
---------word Content End-------
-------word Content Start------
text:这个是文档
imgList:
imgName:图片1
imgName:图片2
imgName:图片3
---------word Content End-------
-------word Content Start------
text:这个是修改过的副本文档
imgList:
imgName:图片1
imgName:图片2
imgName:图片3
---------word Content End-------
-------word Content Start------
text:这个是文档
imgList:
imgName:图片1
imgName:图片2
imgName:图片3
---------word Content End-------

Process finished with exit code 0

由上面可以看到,doc2是通过document.clone()创建的,并且第一次输出的时候2个对象的输出是一样的。而且doc2修改文本内容后不会改变影响document对象的文本内容,这就保证了document的安全性。还需要注意的是,通过clone拷贝对象的时候是不会执行构造函数的

深拷贝和浅拷贝

上面的拷贝被称为浅拷贝,也称作影子拷贝。并不是将所有字段都重新构造了一份,而是通过副本文档的字段引用原始文档的字段。

因此,如果A引用B,那么无论修改A还是B对象,另一个对象都会随之改变。但我们大多数时候都需要A/B对象改变的时候另一个对象不会改变,那么我们如何避免这种情况呢,这边就引出了新的概念:深拷贝

所谓深拷贝,即在拷贝对象时,对于引用型的字段也要采用拷贝的形式,而不是单纯的引用形式。这里就借用上面的demo代码做相关改动,使浅拷贝变成深拷贝。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//深拷贝
@Override 
protected WordDocument clone(){
try {
WordDocument document=(WordDocument) super.clone();
document.mImages=(ArrayList) this.mImages.clone();
document.mText=this.mText;
return document;
}catch (Exception e){
}
return null;
}
//浅拷贝
@Override 
protected WordDocument clone(){
try {
WordDocument document=(WordDocument) super.clone();
document.mImages=this.mImages;
document.mText=this.mText;
return document;
}catch (Exception e){
}
return null;
}

我们可以看到上面的改动对比:深拷贝时调用的clone函数,将中间mImages指向this.mImages的拷贝,而不是它的本身。这样在修改doc2对象时调用addImg()函数就不会影响到document对象了。

so 我们在平时使用的时候尽量要使用深拷贝,以免造成操作副本时影响原始对象的问题