# 4.原型模式

## Prototype（原型）

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

## Prototype（原型）的UML类图

![](https://raw.githubusercontent.com/InnoFang/DesignPatterns/master/uml/prototype.png)

* Clone：抽象类或者接口，声明具备克隆的能力
* ConcretePrototype：具体的原型类

## 原型模式的使用场景

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

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

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

## 原型模式的简单实现

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

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

```java
public class WordDocument implements Cloneable {

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

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


    public String getmText() {
        return text;
    }

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

    public ArrayList<String> getmImages() {
        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;
    }

}
```

解释一下上面的代码

* 首先这个word文档有一段文本和若干图片，图片的描述用List来保存，
* 接着为这些内容实现getter方法和setter方法，并且实现一个showDocument方法用来展示文档内容

  最后，
* 最至关重要的，是实现了Cloneable的clone方法，在这个方法中可以很清晰的看到，当调用这方法时，会调用父类

  的clone()方法来完成WordDocument文档的实例复制，然后将当前WordDocument的内容复制给了clone产生的实例，

  最终完成了WordDocument的复制。

那么，父类的clone方法是什么呢？

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

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

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

```java
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函数中

```java
 ...
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方法

```java
...
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 -------------
```

问题解决了！

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

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

### 原型模式的优缺点：

* 优点：原型模式是内存中二进制流的拷贝，要比直接new一个对象性能好很多，特别是要在一个循环体内产生大量的对象时，原型模式可以更好地体现其优点
* 缺点：由于原型模式是在内存中拷贝，构造函数是不会执行的，在实际开发当中应该注意这个潜在问题

END.

[源码地址](https://github.com/InnoFang/DesignPatterns/tree/master/src/io/innofang/prototype)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://innofang.gitbook.io/oh-my-design-patterns/gitbook/chuang-jian-xing-creational/4.-yuan-xing-mo-shi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
