Skip to the content.

Prototype(原型)

原型模式,是利用已有的实例对象通过克隆的方式,复制出一个具有相同属性的对象,起作用在于 当在面临创建一个复杂对象或实例时,通过克隆可以是程序更高效的运行,保证了程序的效率,因此 原型模式也是一种创建型模式

Prototype(原型)的UML类图

原型模式的使用场景

关于原型模式,有一个需要注意的地方,即 浅拷贝 和 深拷贝

那么这两个什么区别呢?这里暂且不提,当我们通过一个简单例子介绍了原型模式过后,再来介绍

原型模式的简单实现

首先,在Java中,我们是使用Cloneable接口作为Prototype

那么,对于具体的原型类,只需要实现该接口即可,这里我们写一个WordDocument类,用来作为具体的原型实现 ,因为对于文档的复制,我们在平常的工作中再熟悉不过了

public class WordDocument implements Cloneable {

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

    public WordDocument() {
        System.out.println("-------------init-------------");
    }


    public String getText() {
        return text;
    }

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

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

    public void setImages(ArrayList<String> mImages) {
        this.images = mImages;
    }

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

    public void showDocument() {
        System.out.println("-------------Start-------------");
        System.out.println("Text : " + text);
        System.out.println("Image List : ");
        for (String mImage : images) {
            System.out.println("Image Name : " + mImage);
        }
        System.out.println("------------- End -------------");
    }

    protected WordDocument clone() {
        try {
            WordDocument doc = (WordDocument) super.clone();
            doc.text = this.text;
            doc.images = this.images;
            return doc;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }

}

解释一下上面的代码

那么,父类的clone方法是什么呢?

首先我们得知道,每一个java类的祖先类都是Object类,所以这个就是调用 的Object的clone方法,但是我们通过源码可以看到Object的clone方法其实是一个本地方法

 public class Object {
    // ...
    protected native Object clone() throws CloneNotSupportedException;
    // ...
 }

好,完成了具体的原型类的实现,我们来测试一下

public class Client {

    public static void main(String[] args) {

        WordDocument originDoc = new WordDocument();
        originDoc.setText("This is a document");
        originDoc.addImage("Image 1");
        originDoc.addImage("Image 2");
        originDoc.addImage("Image 3");
        originDoc.showDocument();

        WordDocument copyDoc = originDoc.clone();
        copyDoc.showDocument();

        copyDoc.setText("This is a copy document");
        copyDoc.showDocument();

        originDoc.showDocument();

    }
}

在测试类中,我们通过clone复制了一个实例,并且这里尝试通过修改 copyDocument 的内容,并显示其内容

输出结果

-------------init-------------
-------------Start-------------
Text : This is a document
Image List :
Image Name : Image 1
Image Name : Image 2
Image Name : Image 3
------------- End -------------
-------------Start-------------
Text : This is a document
Image List :
Image Name : Image 1
Image Name : Image 2
Image Name : Image 3
------------- End -------------
-------------Start-------------
Text : This is a copy document
Image List :
Image Name : Image 1
Image Name : Image 2
Image Name : Image 3
------------- End -------------
-------------Start-------------
Text : This is a document
Image List :
Image Name : Image 1
Image Name : Image 2
Image Name : Image 3
------------- End -------------

根据输出结果,可以发现,当我们克隆产生的对象修改时,其原型不会改变

但,真的是这样吗?

我们再来测试一下,我们给 copyDocument 添加图片信息

仍然是Client类的main函数中

 ...
WordDocument copyDoc = originDoc.clone();
copyDoc.showDocument();

copyDoc.setText("This is a copy document");

// add this line to test the origin document what will happen when the copy document add a image
copyDoc.addImage("a new Image");

copyDoc.showDocument();

originDoc.showDocument();

输出结果:

...
-------------Start-------------
Text : This is a document
Image List :
Image Name : Image 1
Image Name : Image 2
Image Name : Image 3
------------- End -------------
-------------Start-------------
Text : This is a copy document
Image List :
Image Name : Image 1
Image Name : Image 2
Image Name : Image 3
Image Name : a new Image
------------- End -------------
-------------Start-------------
Text : This is a document
Image List :
Image Name : Image 1
Image Name : Image 2
Image Name : Image 3
Image Name : a new Image
------------- End -------------

这时,我们发现,当我们修改了copyDocument类的内容时,originDocument的内容也跟着改变了

那这是为什么呢?

前面我们提到过,浅拷贝 和 深拷贝

那么,先将结论扔出来,之所以会在只修改copyDocument的情况下,originDocument的内容也会改变的原因是, 在这里我们使用的是 浅拷贝

那么什么是浅拷贝,又为什么会出现上面的情况呢?

我们在利用originDocument的clone方法进行拷贝时,在clone方法内部, 可以发现 copy 的 images 的实例只是简单的指向了当前对象(即originDocument的images), 并没有重新构造一个images对象,这时,如果我们修改copyDocument的images时,就是导致originDocument的images的内容 也跟着改变,这就是所谓的浅拷贝

那么,如何解决这个问题呢?

答案是,利用深拷贝,那么怎么利用深拷贝呢?

很简单,只需要将 images 也用clone的方式拷贝过来就好了,

修改WordDocument的clone方法

...
protected WordDocument clone() {
        try {
            WordDocument copy = (WordDocument) super.clone();
            copy.text = this.text;
            // copy.images = this.images;
            copy.images = (ArrayList<String>) this.images.clone();
            return copy;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }

在上面代码中,利用ArrayList的clone方法来完成我们images的clone

然后我们再来测试一下,Client的代码不变,运行程序显示结果如下

-------------init-------------
-------------Start-------------
Text : This is a document
Image List :
Image Name : Image 1
Image Name : Image 2
Image Name : Image 3
------------- End -------------
-------------Start-------------
Text : This is a document
Image List :
Image Name : Image 1
Image Name : Image 2
Image Name : Image 3
------------- End -------------
-------------Start-------------
Text : This is a copy document
Image List :
Image Name : Image 1
Image Name : Image 2
Image Name : Image 3
Image Name : a new Image
------------- End -------------
-------------Start-------------
Text : This is a document
Image List :
Image Name : Image 1
Image Name : Image 2
Image Name : Image 3
------------- End -------------

问题解决了!

那么在拷贝对象时,对于引用型的字段也要采用拷贝的形式,而不是单纯引用的形式,即深拷贝

那么在日常开发中,为了减少错误,在使用原型模式时,建议使用深拷贝

原型模式的优缺点:

END.