設計模式之——原型模式

前言

在 Java 中,我們可以使用 new 關鍵字指定類名來生成類的實例。但是,有的時候,我們也會在不指定類名的前提下生成實例,例如像圖形編輯器中拖動現有的模型工具制作圖形的實例,這種是非常典型的生成實例的過程太過復雜,很難根據類來生成實例場景,因此需要根據現有的實例來生成新的實例。
像這樣根據實例來生成新的實例的模式,我們稱之為 原型模式
在軟件開發過程中,我們經常會遇到需要創建多個相同或者相似對象的情況,因此 原型模式 的使用頻率還是很高的。

定義

定義: 用原型實例指定創建對象的種類,并通過拷貝這些原型創建新的對象。
類型: 類的創建模式

UML圖

在原型模式中涉及 PrototypeConcretePrototypeClient 三個角色。

prototype.png

代碼如下:

/**
 * @author zhangguoji
 * 2017/8/4 21:46
 */
interface Prototype {
    Prototype clone();
}

class ConcretePrototype implements Prototype {

    private String attr; // 成員屬性

    /**
     * 克隆方法
     */
    @Override
    public ConcretePrototype clone() {
        // 創建新對象
        ConcretePrototype prototype = new ConcretePrototype();
        prototype.setAttr(this.attr);
        return prototype;
    }

    @Override
    public String toString() {
        return "ConcretePrototype[attr=" + attr + "]";
    }

    public void setAttr(String attr) {
        this.attr = attr;
    }

    public String getAttr() {
        return this.attr;
    }
}

public class Client {
    public static void main(String[] args) {
        ConcretePrototype prototype = new ConcretePrototype();
        prototype.setAttr("Kevin Zhang");

        ConcretePrototype prototype2 = prototype.clone();

        System.out.println(prototype.toString());
        System.out.println(prototype2.toString());
    }
}

輸出結果:

ConcretePrototype[attr=Kevin Zhang]
ConcretePrototype[attr=Kevin Zhang]

Process finished with exit code 0

Java的原型模式

Java 中使用原型模式很簡單, 它為我們提供了復制實例的 clone() 方法。

實際上,所有的 Java 類都繼承自 java.lang.Object。Object 類提供一個 clone() 方法,因此,在 Java 中可以直接使用 Object 提供的 clone() 方法來實現對象的克隆。

值得注意的是,被復制對象的類必須實現 java.lang.Cloneable 接口,如果沒有實現 java.lang.Cloneable 接口的實例調用了 clone() 方法,會在運行時拋出CloneNotSupportedException 異常。

Cloneable 是一個標記接口,在 Cloneable 接口中并沒有聲明任何方法,它只是被用來標記可以使用 clone() 方法進行復制。

public interface Cloneable {
}

案例分析

現在,我有一個消息系統的需求,希望復制原先消息模板進行快速創建,然后可以進行修改后保存。

下面,我們通過 Java 的 clone()方法來實現對象的克隆。

Message 就是具體原型類,復制實現 clone() 方法。
Message代碼:

/**
 * @author zhangguoji
 * @date 2017/8/4 21:56
 */
class Message implements Cloneable{
    private String author;
    private String content;

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    protected Object clone() {
        Message message = null;
        try {
            message = (Message) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return message;
    }

    @Override
    public String toString() {
        return "Message{" +
                "author='" + author + '\'' +
                ", content='" + content + '\'' +
                '}';
    }
}

Client代碼:


/**
 * @author zhangguoji
 * @date 2017/8/4 22:00
 */
public class Client {
    public static void main(String[] args) {
        Message message = new Message();
        message.setAuthor("ZGJ");
        message.setContent("2017-08-04【消息】");

        Message message1 = (Message) message.clone();
        message.setContent("2017-08-05【消息】");

        System.out.println(message);
        System.out.println(message1);
    }
}

結果:

Message{author='ZGJ', content='2017-08-05【消息】'}
Message{author='ZGJ', content='2017-08-04【消息】'}

思考

既然要創建新的實例,為什么不直接使用 new XXX(),而要設計出一個原型模式進行實例的復制呢?

有的時候,我們也會在不指定類名的前提下生成實例,例如像圖形編輯器中拖動現有的模型工具制作圖形的實例,這種是非常典型的生成實例的過程太過復雜,很難根據類來生成實例場景,因此需要根據現有的實例來生成新的實例。
Prototype原型模式是一種創建型設計模式,它主要面對的問題是:“某些結構復雜的對象”的創建工作;由于需求的變化,這些對象經常面臨著劇烈的變化,但是他們卻擁有比較穩定一致的接口。

還比如,類初始化需要消化非常多的資源,我們也可以考慮使用原型模式。因為,原型模式是在內存進行二進制流的拷貝,要比直接 new 一個對象性能好很多。

克隆的對象是全新的嗎?

原型模式通過 clone() 方法創建的對象是全新的對象,它在內存中擁有新的地址,通過==符號判斷返回是flase。

深克隆和淺克隆

clone() 方法使用的是淺克隆。淺克隆對于要克隆的對象, 會復制其基本數據類型和 String 類型或其他final類型的屬性的值給新的對象. 而對于非基本數據類型的屬性,例如數組、集合, 僅僅復制一份引用給新產生的對象, 即新產生的對象和原始對象中的非基本數據類型的屬性都指向的是同一個對象。

此外,還存在深克隆。深克隆對于要克隆的對象, 對于非基本數據類型的屬性,例如數組、集合支持復制。換句話說,在深克隆中,除了對象本身被復制外,對象所包含的所有成員變量也將復制。

淺克隆和深克隆的主要區別在于,是否支持引用類型的成員變量的復制。

在 Java 中,如果需要實現深克隆,可以通過序列化等方式來實現。
我們在消息中增加一個附件類


import java.io.Serializable;

/**
 * @author zhangguoji
 * @date 2017/8/4 22:10
 */
public class Attachment implements Serializable {
    private static final long serialVersionUID = 1L;

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Attachment{" +
                "name='" + name + '\'' +
                '}';
    }
}

在消息中加入這個附件類


import java.io.*;

/**
 * @author zhangguoji
 * 2017/8/4 21:56
 */
class Message implements Serializable {
    private static final long serialVersionUID = 1L;
    private String author;
    private String content;
    private Attachment attachment;

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Attachment getAttachment() {
        return attachment;
    }

    public void setAttachment(Attachment attachment) {
        this.attachment = attachment;
    }

    public Message deepClone() throws IOException, ClassNotFoundException {
        /**
         * 將對象序列化到對象數組流中
         */
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);

        /**
         * 從流中讀取對象
         */
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return (Message) ois.readObject();
    }
}

使用序列化實現深度克隆,然后再客戶端中測試是否深度克隆成功


import java.io.IOException;

/**
 * @author zhangguoji
 * @date 2017/8/4 22:30
 */
public class Client {
    public static void main(String[] args) {
        Message message = new Message();
        message.setAuthor("ZGJ");
        message.setContent("2017-08-04【消息】");
        Attachment attachment = new Attachment();
        attachment.setName("附件");
        message.setAttachment(attachment);
        Message message1 = null;
        try {
            message1 = message.deepClone();
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("克隆失敗");
        }
        System.out.println("消息是否相同:" + (message == message1));
        System.out.println("附件是否相同:" + (message.getAttachment() == message1.getAttachment()));
    }
}

結果如下:

消息是否相同:false
附件是否相同:false

注意

一般而言,Java語言中的clone()方法滿足:

  1. 對任何對象x,都有x.clone() != x,即克隆對象與原型對象不是同一個對象;
  2. 對任何對象x,都有x.clone().getClass() == x.getClass(),即克隆對象與原型對象的類型一樣;
  3. 如果對象x的equals()方法定義恰當,那么x.clone().equals(x)應該成立。

為了獲取對象的一份拷貝,我們可以直接利用Object類的clone()方法,具體步驟如下:

  1. 在派生類中覆蓋基類的clone()方法,并聲明為public;
  2. 在派生類的clone()方法中,調用super.clone();
    3.派生類需實現Cloneable接口。
    此時,Object類相當于抽象原型類,所有實現了Cloneable接口的類相當于具體原型類。

總結

原型模式的目的在于,根據實例來生成新的實例,我們可以很方便的快速的創建實例。

在 Java 中使用原型模式很簡單, 被復制對象的類必須實現 java.lang.Cloneable 接口,并重寫 clone() 方法。

使用原型模式的時候,尤其需要注意淺克隆和深克隆問題。在 Java 中,如果需要實現深克隆,可以通過序列化等方式來實現

個人博客: http://blog.zgj12138.cn
CSDN: http://blog.csdn.net/zgj12138

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容