悟空模式-java-原型模式

来源:http://www.cnblogs.com/tirion/archive/2017/09/30/7597870.html
-Advertisement-
Play Games

【卻說那妖精與大聖鬥經半日,不分勝敗。行者把棒丟起,叫一聲“變!”就以一變十,以十變百,以百變千,半天里,好似蛇游蟒攪,亂打妖邪。妖邪慌了手腳,將身一閃,化道清風,即奔碧空之上逃走。行者念聲咒語,將鐵棒收做一根,縱祥光一直趕來。】 在西游記第九十五回【假合真形擒玉兔 真陰歸正會靈元】中,孫行者“毆打 ...


卻說那妖精與大聖鬥經半日,不分勝敗。行者把棒丟起,叫一聲“變!”就以一變十,以十變百,以百變千,半天里,好似蛇游蟒攪,亂打妖邪。妖邪慌了手腳,將身一閃,化道清風,即奔碧空之上逃走。行者念聲咒語,將鐵棒收做一根,縱祥光一直趕來。

在西游記第九十五回【假合真形擒玉兔 真陰歸正會靈元】中,孫行者“毆打”玉兔精的時候,將如意金箍棒從一根化作了千百根,打得玉兔精無從招架。

這千百根金箍棒的屬性應該是一樣的,如果孫悟空每次都要新建一個新的金箍棒對象,然後把原有的金箍棒的屬性複製過去,如此重覆千百次,未免太過麻煩,所以我們這裡假設孫悟空使用了原型模式來創建多個相同屬性的金箍棒實例。

在詳細介紹原型模式之前,我們需要先瞭解一下java.lang.Object#clone()方法以及java.lang.Cloneable介面的功能及實現:

java.lang.Cloneable

/**
 * A class implements the <code>Cloneable</code> interface to
 * indicate to the {@link java.lang.Object#clone()} method that it
 * is legal for that method to make a
 * field-for-field copy of instances of that class.
 * <p>
 * Invoking Object's clone method on an instance that does not implement the
 * <code>Cloneable</code> interface results in the exception
 * <code>CloneNotSupportedException</code> being thrown.
 * <p>
 * By convention, classes that implement this interface should override
 * <tt>Object.clone</tt> (which is protected) with a public method.
 * See {@link java.lang.Object#clone()} for details on overriding this
 * method.
 * <p>
 * Note that this interface does <i>not</i> contain the <tt>clone</tt> method.
 * Therefore, it is not possible to clone an object merely by virtue of the
 * fact that it implements this interface.  Even if the clone method is invoked
 * reflectively, there is no guarantee that it will succeed.
 *
 * @author  unascribed
 * @see     java.lang.CloneNotSupportedException
 * @see     java.lang.Object#clone()
 * @since   JDK1.0
 */
public interface Cloneable {
}

Java doc的意思大致是說:一個類實現了Cloneable介面,就是在運行時向虛擬機表明當前類可以合法地使用Object類的clone()方法,來進行對象內容的拷貝。假設沒有實現Cloneable介面就調用clone()方法的話,雖然能夠通過編譯,但是會在運行時拋出java.lang.CloneNotSupportedException。一般來說,實現Cloneable介面的類需要重寫Object類的protected方法,並且聲明重寫方法為public的。需要註意的是,Cloneable介面並不包含clone()方法。因此,一個類僅僅實現Cloneable介面就想成功實現clone()功能是不可能的。即使反射調用也不保證會成功。

也就是說,要想調用源生的Object類的clone()方法,我們必須讓原型類實現Cloneable介面。那麼,Object類的clone()的優勢在哪裡呢?

/**
     * Creates and returns a copy of this object.  The precise meaning
     * of "copy" may depend on the class of the object. The general
     * intent is that, for any object {@code x}, the expression:
     * <blockquote>
     * <pre>
     * x.clone() != x</pre></blockquote>
     * will be true, and that the expression:
     * <blockquote>
     * <pre>
     * x.clone().getClass() == x.getClass()</pre></blockquote>
     * will be {@code true}, but these are not absolute requirements.
     * While it is typically the case that:
     * <blockquote>
     * <pre>
     * x.clone().equals(x)</pre></blockquote>
     * will be {@code true}, this is not an absolute requirement.
     * <p>
     * By convention, the returned object should be obtained by calling
     * {@code super.clone}.  If a class and all of its superclasses (except
     * {@code Object}) obey this convention, it will be the case that
     * {@code x.clone().getClass() == x.getClass()}.
     * <p>
     * By convention, the object returned by this method should be independent
     * of this object (which is being cloned).  To achieve this independence,
     * it may be necessary to modify one or more fields of the object returned
     * by {@code super.clone} before returning it.  Typically, this means
     * copying any mutable objects that comprise the internal "deep structure"
     * of the object being cloned and replacing the references to these
     * objects with references to the copies.  If a class contains only
     * primitive fields or references to immutable objects, then it is usually
     * the case that no fields in the object returned by {@code super.clone}
     * need to be modified.
     * <p>
     * The method {@code clone} for class {@code Object} performs a
     * specific cloning operation. First, if the class of this object does
     * not implement the interface {@code Cloneable}, then a
     * {@code CloneNotSupportedException} is thrown. Note that all arrays
     * are considered to implement the interface {@code Cloneable} and that
     * the return type of the {@code clone} method of an array type {@code T[]}
     * is {@code T[]} where T is any reference or primitive type.
     * Otherwise, this method creates a new instance of the class of this
     * object and initializes all its fields with exactly the contents of
     * the corresponding fields of this object, as if by assignment; the
     * contents of the fields are not themselves cloned. Thus, this method
     * performs a "shallow copy" of this object, not a "deep copy" operation.
     * <p>
     * The class {@code Object} does not itself implement the interface
     * {@code Cloneable}, so calling the {@code clone} method on an object
     * whose class is {@code Object} will result in throwing an
     * exception at run time.
     *
     * @return     a clone of this instance.
     * @throws  CloneNotSupportedException  if the object's class does not
     *               support the {@code Cloneable} interface. Subclasses
     *               that override the {@code clone} method can also
     *               throw this exception to indicate that an instance cannot
     *               be cloned.
     * @see java.lang.Cloneable
     */
    protected native Object clone() throws CloneNotSupportedException;

我們看到clone()方法是一個native方法,native方法的效率一般遠高於非native方法。同時我們也可以看到關於clone()方法的描述也印證了Cloneable介面的相關介紹,如protected以及CloneNotSupportedException等。

關於clone()方法的表現如下:

x.clone() !=x;

x.clone().getClass() == x.getClass();

x.clone().equals(x) == true;

這裡還要介紹關於深複製與淺複製的概念:

淺複製對象的所有屬性都與原對象具有相同的值,包括引用其他對象的變數,對這些對象的引用依然指向原來的對象。

而深複製對象會將原對象的所有屬性都複製一遍,包括原對象引用的對象,深複製會複製新的引用對象作為自己的變數而不使用原來的對象。

 

淺複製原型模式

package com.tirion.design.prototype;

public class GoldenCudgel implements Cloneable {

    public GoldenCudgel() {

    }

    public GoldenCudgel(boolean disappear) {
        this.disappear = disappear;
    }

    private boolean disappear;

    public boolean isDisappear() {
        return disappear;
    }

    public void setDisappear(boolean disappear) {
        this.disappear = disappear;
    }

    public GoldenCudgel clone() {
        GoldenCudgel goldenCudgel = null;
        try {
            goldenCudgel = (GoldenCudgel) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return goldenCudgel;
    }

    public boolean equals(GoldenCudgel obj) {
        return obj.isDisappear() == disappear;
    }

}

悟空

package com.tirion.design.prototype;

public class WuKong {

    private static GoldenCudgel goldenCudgel = new GoldenCudgel(false);

    public static void main(String[] args) {
        GoldenCudgel copyGoldenCudgel = goldenCudgel.clone();
        System.out.println(goldenCudgel);
        System.out.println(copyGoldenCudgel);
        System.out.println(goldenCudgel != copyGoldenCudgel);
        System.out.println(goldenCudgel.getClass() == copyGoldenCudgel.getClass());
        System.out.println(goldenCudgel.equals(copyGoldenCudgel));
    }

}

列印結果

com.tirion.design.prototype.GoldenCudgel@74a14482
com.tirion.design.prototype.GoldenCudgel@1540e19d
true
true
true

在金箍棒GoldenCudgel中,我們不僅提供了clone()方法的實現,還重寫了queals()方法用於檢驗複製結果。

我們可以看到,孫悟空在創建新的金箍棒對象時,調用自身持有的金箍棒的clone()方法,就得到了一個新的金箍棒對象,它的屬性值disappear(是否消失)的值在複製後保持不變(如果金箍棒有其他更多屬性,也會保持不變,這裡我們不過多贅述)。

如果孫悟空要複製一千根金箍棒,那麼他就調用一千次自身持有的金箍棒的clone()方法即可。

通過原型模式,我們可以通過調用原型複製方法,不需要手動設置屬性,就可以達到產生與原對象相同屬性的對象的目的,大大簡化了我們創建原型對象的工作量。

值得註意的是,原型模式是一種對象的創建模式,它並沒有要求必須要通過Cloneable介面來完成,當你為一個類提供一個複製自身的方法,所有要創建相同屬性的該類對象的使用者,都通過該方法來創建新對象,那麼也是使用了原型模式,只是沒有實現Cloneable方便安全而已。

深複製原型模式

我們都知道,孫悟空除了著名的筋斗雲、火眼金睛和七十二變之外,還有很多其他的法術,比如身外身法術,就是產生一個自身的複製,下麵我們來看孫悟空的深複製與淺複製的區別。

新的悟空對象,提供了淺複製與深複製兩個複製方法

package com.tirion.design.prototype;

public class WuKong implements Cloneable {

    private GoldenCudgel goldenCudgel;

    public GoldenCudgel getGoldenCudgel() {
        return goldenCudgel;
    }

    public void setGoldenCudgel(GoldenCudgel goldenCudgel) {
        this.goldenCudgel = goldenCudgel;
    }

    public WuKong() {
        goldenCudgel = new GoldenCudgel(false);
    }

    public WuKong clone() {
        WuKong wuKong = null;
        try {
            wuKong = (WuKong) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return wuKong;
    }

    public WuKong deepClone() {
        WuKong wuKong = null;
        try {
            wuKong = (WuKong) super.clone();
            wuKong.setGoldenCudgel(wuKong.getGoldenCudgel().clone());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return wuKong;
    }

    public static void main(String[] args) {
        WuKong wuKong = new WuKong();
        WuKong wuKongCopy = wuKong.clone();
        System.out.println("淺複製後悟空是否為同一個對象" + (wuKong == wuKongCopy));
        System.out.println("淺複製後金箍棒是否為同一個對象" + (wuKong.getGoldenCudgel() == wuKongCopy.getGoldenCudgel()));
        System.out.println("淺複製原對象金箍棒屬性為" + wuKong.getGoldenCudgel().isDisappear());
        System.out.println("淺複製對象金箍棒屬性發生改變...");
        wuKongCopy.getGoldenCudgel().setDisappear(true);
        System.out.println("淺複製原對象金箍棒屬性為" + wuKong.getGoldenCudgel().isDisappear());
        System.out.println("狀態重置...");
        wuKong.getGoldenCudgel().setDisappear(false);
        WuKong wuKongDeepCopy = wuKong.deepClone();
        System.out.println("深複製後悟空是否為同一個對象" + (wuKong == wuKongDeepCopy));
        System.out.println("深複製後金箍棒是否為同一個對象" + (wuKong.getGoldenCudgel() == wuKongDeepCopy.getGoldenCudgel()));
        System.out.println("深複製原對象金箍棒屬性為" + wuKong.getGoldenCudgel().isDisappear());
        System.out.println("深複製對象金箍棒屬性發生改變...");
        wuKongDeepCopy.getGoldenCudgel().setDisappear(true);
        System.out.println("深複製原對象金箍棒屬性為" + wuKong.getGoldenCudgel().isDisappear());
    }

}

執行結果:

淺複製後悟空是否為同一個對象false
淺複製後金箍棒是否為同一個對象true
淺複製原對象金箍棒屬性為false
淺複製對象金箍棒屬性發生改變...
淺複製原對象金箍棒屬性為true
狀態重置...
深複製後悟空是否為同一個對象false
深複製後金箍棒是否為同一個對象false
深複製原對象金箍棒屬性為false
深複製對象金箍棒屬性發生改變...
深複製原對象金箍棒屬性為false

從執行結果中我們看到,淺複製後雖然悟空的複製對象與原對象不是同一個對象,但是兩個悟空持有的金箍棒是同一個對象,當複製對象的金箍棒消失時,原悟空對象的金箍棒也相應消失了,這顯然與我們的認知不符合,這時候,就需要深複製。

在深複製中,我們將需要深複製的屬性也實現了Cloneable介面,在這裡就是金箍棒類,在深複製deepClone方法中,我們不僅僅將悟空對象克隆了,同時也將需要深複製的對象克隆了一份,這樣,深複製後,兩個悟空持有的金箍棒就不是同一個了,複製對象的金箍棒消失,並不影響原悟空對象的金箍棒。

這裡也存在一個問題,就是當對象的屬性非常複雜的時候,我們的各個屬性都要去實現Cloneable介面,且deepClone()方法會相當複雜。

下麵我們看一下有沒有更加簡單的深複製方式

Java對象序列化可以將對象轉化為一個位元組序列,並能夠通過反序列化將位元組序列恢復為原來的對象,我們可以利用這一功能來實現輕量級的深複製,但前提是需要複製對象實現Serializable介面。

序列化深複製原型模式

悟空

package com.tirion.design.prototype;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class WuKong implements Serializable {

    private GoldenCudgel goldenCudgel;

    public GoldenCudgel getGoldenCudgel() {
        return goldenCudgel;
    }

    public void setGoldenCudgel(GoldenCudgel goldenCudgel) {
        this.goldenCudgel = goldenCudgel;
    }

    public WuKong() {
        goldenCudgel = new GoldenCudgel(false);
    }

    public WuKong deepClone() throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(this);
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        return (WuKong) ois.readObject();
    }

    public static void main(String[] args) throws Exception {
        WuKong wuKong = new WuKong();
        WuKong wuKongDeepCopy = wuKong.deepClone();
        System.out.println("深複製後悟空是否為同一個對象" + (wuKong == wuKongDeepCopy));
        System.out.println("深複製後金箍棒是否為同一個對象" + (wuKong.getGoldenCudgel() == wuKongDeepCopy.getGoldenCudgel()));
        System.out.println("深複製原對象金箍棒屬性為" + wuKong.getGoldenCudgel().isDisappear());
        System.out.println("深複製對象金箍棒屬性發生改變...");
        wuKongDeepCopy.getGoldenCudgel().setDisappear(true);
        System.out.println("深複製原對象金箍棒屬性為" + wuKong.getGoldenCudgel().isDisappear());
    }

}

這時候運行main()方法會報java.io.NotSerializableException,因為序列化對象要求引用對象也必須實現Serializable介面,除非對應屬性不需要序列化,所以我們這裡需要將金箍棒類也實現序列化介面。

金箍棒

package com.tirion.design.prototype;

import java.io.Serializable;

public class GoldenCudgel implements Serializable {

    public GoldenCudgel() {

    }

    public GoldenCudgel(boolean disappear) {
        this.disappear = disappear;
    }

    private boolean disappear;

    public boolean isDisappear() {
        return disappear;
    }

    public void setDisappear(boolean disappear) {
        this.disappear = disappear;
    }

    public boolean equals(GoldenCudgel obj) {
        return obj.isDisappear() == disappear;
    }

}

執行結果

深複製後悟空是否為同一個對象false
深複製後金箍棒是否為同一個對象false
深複製原對象金箍棒屬性為false
深複製對象金箍棒屬性發生改變...
深複製原對象金箍棒屬性為false

從結果來看,通過序列化實現深複製與通過clone()方法實現深複製的結果是一樣的,但是方法卻比較簡單,我們只需要將需要複製的對象實現序列化介面就可以了。同時java的對象序列化是提供了輕量級持久化的,我們可以通過網路或者磁碟來進行數據的傳播及持久化,並且就突破了clone()方法只能本地程式運行期間才能持久化的限制。

關於原型模式的介紹就到這裡,你可以將它記憶為身外身模式

如果你認為文章中哪裡有錯誤或者不足的地方,歡迎在評論區指出,也希望這篇文章對你學習java設計模式能夠有所幫助。轉載請註明,謝謝。

更多設計模式的介紹請到悟空模式-java設計模式中查看。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 方法一: Toolkit.getDefaultToolkit().beep(); 方法二: System.out.println('\007');//八進位數 ...
  • package com.swift;//可以不要這句 import java.io.IOException; public class Shutdown100 { public static void main(String[] args) { try { Runtime.getRuntime().... ...
  • Doing Homework HDU - 1074 題意: 有n個作業,每個作業有一個截止時間和完成所需時間,如果完成某個作業的時間超出了截止時間就扣完成時間-截止時間的分。求按怎樣的順序完成作業扣分最少。 方法:狀壓dp。ans[S]表示完成集合S的作業最少的扣分(集合S用一個數字表示)。pre[ ...
  • 一、JSTL的概述 1、Apache開發與維護,依賴EL表達式 2、Apache Tomcat安裝JSTL 庫步驟如下: 從Apache的標準標簽庫中下載的二進包(jakarta-taglibs-standard-current.zip)。 官方下載地址:http://archive.apache. ...
  • Buy Tickets Problem Description Railway tickets were difficult to buy around the Lunar New Year in China, so we must get up early and join a long queu ...
  • 關於裝飾器的更多信息可以參考http://egon09.blog.51cto.com/9161406/1836763 1.裝飾器Decorator裝飾器:本質上是函數,(裝飾其他函數),就是為其他函數添加附加功能 原則:不能修改被裝飾函數的源代碼;不能修改被裝飾函數的調用方式#實現裝飾器的知識儲備:... ...
  • 歷經千辛萬苦,整理了軟體開發過程中必備英文單詞,助你走向編程巔峰 !!! 連最難的英文單詞你都征服了,你還怕什麼? (拒絕死記硬背,平時多看看,多用於代碼中,) 【不求全部都會,但求蒙的都對!】 第一天 application [ˌæplɪ'keɪʃ(ə)n]應用程式 應用、應用程式 applica ...
  • 當前環境 代碼地址 git 地址:https://github.com/jasonGeng88/java-network-programming 背景 前不久,上線了一個新項目,這個項目是一個壓測系統,可以簡單的看做通過回放詞表(http請求數據),不斷地向服務發送請求,以達到壓測服務的目的。在測試 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...