java設(shè)計(jì)模式-原型模式(Prototype)

定義

原型模式屬于對(duì)象的創(chuàng)建模式腔呜。通過(guò)給出一個(gè)原型對(duì)象來(lái)指明所有創(chuàng)建的對(duì)象的類(lèi)型,然后用復(fù)制這個(gè)原型對(duì)象的辦法創(chuàng)建出更多同類(lèi)型的對(duì)象。這就是原型模式的用意

原型模式的結(jié)構(gòu)

原型模式要求對(duì)象實(shí)現(xiàn)同一個(gè)可以“克隆”自身的接口榄鉴,遮掩個(gè)就可以通過(guò)賦值一個(gè)實(shí)例對(duì)象本身來(lái)創(chuàng)建一個(gè)新的實(shí)例苍碟。

這樣一來(lái)赞季,通過(guò)原型實(shí)例創(chuàng)建新的對(duì)象沈跨,就不再需要關(guān)心這個(gè)實(shí)例本身的類(lèi)型篮奄,只要實(shí)現(xiàn)了克隆自身的方法,就可以通過(guò)這個(gè)方法獲取新的對(duì)象穆趴,而無(wú)需再去通過(guò)new來(lái)創(chuàng)建脸爱。

原型對(duì)象有兩種表現(xiàn)形式:

  1. 簡(jiǎn)單形式
  2. 登記形式

這兩種形式僅僅是原型模式的不同實(shí)現(xiàn)。

簡(jiǎn)單形式的原型模式

原型模式

原型模式涉及三個(gè)角色:

  1. 客戶(Client)角色:客戶類(lèi)提出創(chuàng)建對(duì)象的請(qǐng)求未妹。
  2. 抽象原型(Prototype)角色:這是一個(gè)抽象角色阅羹,通常由一個(gè)Java接口或者Java抽象類(lèi)實(shí)現(xiàn)勺疼。此角色給出所有的具體原型類(lèi)所需的接口教寂。
  3. 具體原型(Concrete Prototype)角色:被復(fù)制的對(duì)象捏鱼。此角色需要實(shí)現(xiàn)抽象原型角色要求的接口。

示例代碼

抽象原型角色

/**
 * 抽象原型角色
 */
public abstract class Prototype {
    private String id;
    
    public Prototype(String id) {
        this.id = id;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    /**
     * 克隆自身的方法
     * @return 一個(gè)從自身克隆出來(lái)的對(duì)象酪耕。
     */
    public abstract Prototype clone();
}

具體原型角色

public class ConcreteProtype1 extends Prototype {
    public ConcreteProtype1(String id) {
        super(id);
    }

    public Prototype clone() {
        Prototype prototype = new ConcreteProtype1(this.getId());
        return prototype;
    }
}
public class ConcreteProtype2 extends Prototype {
    public ConcreteProtype2(String id) {
        super(id);
    }

    public Prototype clone() {
        Prototype prototype = new ConcreteProtype2(this.getId());
        return prototype;
    }
}

客戶端

public class Client {
    public static void main(String[] args) {
        ConcreteProtype1 protype1 = new ConcreteProtype1("Protype1");
        ConcreteProtype1 protypeCopy1 = (ConcreteProtype1)protype1.clone();
        System.out.println(protypeCopy1.getId());
        System.err.println(protype1.toString());
        System.err.println(protypeCopy1.toString());

        ConcreteProtype2 protype2 = new ConcreteProtype2("Protype2");
        ConcreteProtype2 protypeCopy2 = (ConcreteProtype2)protype2.clone();
        System.out.println(protypeCopy2.getId());
        System.err.println(protype2.toString());
        System.err.println(protypeCopy2.toString());
    }
}

輸出結(jié)果:

Protype1
com.sschen.prototype.ConcreteProtype1@2a139a55
com.sschen.prototype.ConcreteProtype1@15db9742
Protype2
com.sschen.prototype.ConcreteProtype2@6d06d69c
com.sschen.prototype.ConcreteProtype2@7852e922

還有另外一種對(duì)象的復(fù)制方式导梆,如下:

        ConcreteProtype1 protype3 = new ConcreteProtype1("Protype3");
        ConcreteProtype1 protypeCopy3 = protype3;
        System.out.println(protypeCopy3.getId());
        System.err.println(protype3.toString());
        System.err.println(protypeCopy3.toString());

這種方式的輸出結(jié)果為:

Protype3
com.sschen.prototype.ConcreteProtype1@2a139a55
com.sschen.prototype.ConcreteProtype1@2a139a55

這種方式同上面的原型模式的克隆模式比較,可以看見(jiàn)的是:原型模式生成的兩個(gè)對(duì)象迂烁,內(nèi)存地址是不一樣的看尼。

之前在java面試 內(nèi)存中堆和棧的區(qū)別文章中說(shuō)明過(guò):對(duì)象的值存放在堆中,在棧中存儲(chǔ)指向堆中內(nèi)存位置的引用盟步。

第一種方式中藏斩,我們是重新創(chuàng)建了新的對(duì)象,對(duì)象的值和引用都是新的却盘,對(duì)于克隆對(duì)象的任何變更狰域,都不會(huì)影響到原對(duì)象的值。如下圖:

原型模式存儲(chǔ)結(jié)構(gòu)圖

另一種方式黄橘,我們只是在棧中新創(chuàng)建了一個(gè)引用兆览,指向的還是被復(fù)制對(duì)象對(duì)應(yīng)的堆地址,對(duì)于復(fù)制對(duì)象的變更塞关,都會(huì)同時(shí)改變?cè)瓕?duì)象的值抬探。如下圖:

不正確的對(duì)象復(fù)制方式

登記形式的原型模型

登記形式的原型模型

作為原型模式的第二種形式,它多了一個(gè)原型管理器(PrototypeManager)角色帆赢。該角色的作用是:創(chuàng)建具體有原型類(lèi)的對(duì)象小压,并記錄每一個(gè)被創(chuàng)建的對(duì)象。

示例代碼

抽象原型角色

public interface Prototype {
    public Prototype clone();
    public String getName();
    public void setName(String name);
}

具體原型角色

public class ConcretePrototype1 implements Prototype {

    private String name;
    
    @Override
    public Prototype clone() {
        ConcretePrototype1 prototype1 = new ConcretePrototype1();
        prototype1.setName(this.name);
        return prototype1;
    }

    @Override
    public String getName() {
        return this.name;
    }

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

    @Override
    public String toString() {
        return "ConcretePrototype1 [name=" + name + "]";
    }
}
public class ConcretePrototype2 implements Prototype {

    private String name;

    @Override
    public Prototype clone() {
        ConcretePrototype2 prototype2 = new ConcretePrototype2();
        prototype2.setName(this.name);
        return prototype2;
    }

    @Override
    public String getName() {
        return this.name;
    }

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

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

原型管理器角色保持一個(gè)聚集椰于,作為對(duì)所有原型對(duì)象的登記怠益,這個(gè)角色提供必要的方法,供外界增加新的原型對(duì)象和取得已經(jīng)登記過(guò)的原型對(duì)象廉羔。

public class PrototypeManager {
    /**
     * 用來(lái)記錄原型的編號(hào)同原型實(shí)例的對(duì)象關(guān)系
     */
    private static Map<String, Prototype> map = new HashMap<String, Prototype>();
    
    /**
     * 私有化構(gòu)造方法溉痢,避免從外部創(chuàng)建實(shí)例
     */
    private PrototypeManager() {}
    
    /**
     * 向原型管理器里面添加或者修改原型實(shí)例
     * @param prototypeId 原型編號(hào)
     * @param prototype 原型實(shí)例
     */
    public synchronized static void setPrototype(String prototypeId, Prototype prototype) {
        map.put(prototypeId, prototype);
    }
    
    /**
     * 根據(jù)原型編號(hào)從原型管理器里面移除原型實(shí)例
     * @param prototypeId 原型編號(hào)
     */
    public synchronized static void removePrototype(String prototypeId) {
        map.remove(prototypeId);
    }
    
    /**
     * 根據(jù)原型編號(hào)獲取原型實(shí)例
     * @param prototypeId 原型編號(hào)
     * @return 原型實(shí)例對(duì)象
     * @throws Exception 如果根據(jù)原型編號(hào)無(wú)法獲取對(duì)應(yīng)實(shí)例,則提示異潮锼“您希望獲取的原型還沒(méi)有注冊(cè)或已被銷(xiāo)毀”
     */
    public synchronized static Prototype getPrototype(String prototypeId) throws Exception {
        Prototype prototype = map.get(prototypeId);
        
        if (prototype == null) {
            throw new Exception("您希望獲取的原型還沒(méi)有注冊(cè)或已被銷(xiāo)毀");
        }
        
        return prototype;
    }
}

客戶端角色

public class Client {
    public static void main(String[] args) {
        try {
            Prototype p1 = new ConcretePrototype1();
            PrototypeManager.setPrototype("p1", p1);
            //獲取原型來(lái)創(chuàng)建對(duì)象
            Prototype p3 = PrototypeManager.getPrototype("p1").clone();
            p3.setName("張三");
            System.out.println("第一個(gè)實(shí)例:" + p3);
            
            //有人動(dòng)態(tài)的切換了實(shí)現(xiàn)
            Prototype p2 = new ConcretePrototype2();
            PrototypeManager.setPrototype("p1", p2);
            
            //重新獲取原型來(lái)創(chuàng)建對(duì)象
            Prototype p4 = PrototypeManager.getPrototype("p1").clone();
            p4.setName("李四");
            System.out.println("第二個(gè)實(shí)例:" + p4);
            
            //有人注銷(xiāo)了這個(gè)原型
            PrototypeManager.removePrototype("p1");
            
            //再次獲取原型來(lái)創(chuàng)建對(duì)象
            Prototype p5 = PrototypeManager.getPrototype("p1").clone();
            p5.setName("王五");
            System.out.println("第三個(gè)實(shí)例:" + p5);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

輸出結(jié)果為:

第一個(gè)實(shí)例:ConcretePrototype1 [name=張三]
第二個(gè)實(shí)例:ConcretePrototype2 [name=李四]
java.lang.Exception: 您希望獲取的原型還沒(méi)有注冊(cè)或已被銷(xiāo)毀
    at com.sschen.prototyperegist.PrototypeManager.getPrototype(PrototypeManager.java:44)
    at com.sschen.prototyperegist.Client.main(Client.java:26)

兩種形式的比較

簡(jiǎn)單形式和登記形式的原型模式各有其長(zhǎng)處和短處孩饼。

  • 如果需要?jiǎng)?chuàng)建的原型對(duì)象數(shù)據(jù)較少而且比較固定的話,可以采用第一種形式竹挡。在這種情況下镀娶,原型對(duì)象的引用可以由客戶端自己保存。
  • 如果要?jiǎng)?chuàng)建的原型對(duì)象數(shù)據(jù)不固定的話揪罕,可以采用第二種形式梯码。在這種情況下宝泵,客戶端不保存對(duì)原型對(duì)象的引用,這個(gè)任務(wù)被交給原型管理器角色轩娶。在克隆一個(gè)對(duì)象之前儿奶,客戶端可以查看管理員對(duì)象是否已經(jīng)有一個(gè)滿足要求的原型對(duì)象。如果有鳄抒,可以從原型管理器角色中取得這個(gè)對(duì)象引用闯捎;如果沒(méi)有,客戶端就需要自行復(fù)制此原型對(duì)象许溅。

Java中的克隆方法

Java中的所有類(lèi)都是從java.lang.Object類(lèi)繼承而來(lái)的瓤鼻,而Object類(lèi)提供protected Object clone()方法對(duì)對(duì)象進(jìn)行克隆復(fù)制,子類(lèi)當(dāng)然也可以把這個(gè)方法置換掉贤重,提供滿足自己需要的復(fù)制方法茬祷。對(duì)象的復(fù)制有一個(gè)基本問(wèn)題,就是對(duì)象通常都有對(duì)其他對(duì)象的引用并蝗。當(dāng)使用Object類(lèi)的clone()方法來(lái)復(fù)制一個(gè)對(duì)象時(shí)祭犯,此對(duì)象對(duì)其他對(duì)象的引用也同時(shí)會(huì)被復(fù)制一份。

java語(yǔ)言提供的Cloneable接口只起一個(gè)作用借卧,就是在運(yùn)行時(shí)期通知Java虛擬機(jī)可以安全的在這個(gè)類(lèi)上使用clone()方法盹憎。通過(guò)調(diào)用這個(gè)clone()方法可以得到一個(gè)對(duì)象的復(fù)制。由于Object類(lèi)本身不實(shí)現(xiàn)Cloneable接口铐刘,因此如果所考慮的類(lèi)沒(méi)有實(shí)現(xiàn)Cloneable接口時(shí)陪每,調(diào)用clone()方法會(huì)拋出CloneNotSupportedException異常。

克隆滿足的條件

clone()方法將對(duì)象復(fù)制了一份并返還給了調(diào)用者镰吵。所謂“復(fù)制”的含義于clone()方法是怎么實(shí)現(xiàn)的含義是一樣的檩禾。一般而言,clone()方法滿足以下的描述:

  1. 對(duì)任何的對(duì)象x疤祭,都有x.clone() != x盼产。換而言之,克隆對(duì)象和原對(duì)象不是同一個(gè)對(duì)象勺馆。
  2. 對(duì)任何的對(duì)象x戏售,都有x.clone().getClass() == x.getClass()。換而言之草穆,克隆對(duì)象同原對(duì)象的類(lèi)型一致灌灾。
  3. 如果對(duì)象xequals()方法定義其恰當(dāng)?shù)脑挘敲?code>x.clone().equals(x)應(yīng)當(dāng)是成立的悲柱。

在Java語(yǔ)言的API中锋喜,凡是提供了clone()方法的類(lèi),都滿足上面的這些條件。Java語(yǔ)言的設(shè)計(jì)師再設(shè)計(jì)自己的clone()方法時(shí)嘿般,也應(yīng)當(dāng)遵守這三個(gè)條件段标。一般來(lái)說(shuō),上面的三個(gè)條件中的前兩個(gè)是必需的炉奴,而第三個(gè)是可選的逼庞。

淺克隆和深克隆

無(wú)論你是自己實(shí)現(xiàn)克隆方法,還是采用Java提供的克隆方法盆佣,都存在一個(gè)淺度克隆和深度克隆的問(wèn)題往堡。

  • 淺度克隆:只負(fù)責(zé)克隆按值傳遞的數(shù)據(jù)(比如基本數(shù)據(jù)類(lèi)型,String類(lèi)型)共耍,而不是復(fù)制它所引用的對(duì)象。換而言之吨瞎,所有對(duì)其他對(duì)象的引用都仍然指向原來(lái)的對(duì)象痹兜。
  • 深度克隆:除了淺度克隆需要克隆的值外,還負(fù)責(zé)克隆引用類(lèi)型的數(shù)據(jù)颤诀。那些引用其他對(duì)象的變量將指向被復(fù)制過(guò)的新對(duì)象字旭,而不再是原有的那些被引用的對(duì)象。換而言之崖叫,深度克隆要把復(fù)制的對(duì)象所引用的對(duì)象都復(fù)制一遍遗淳,而這種對(duì)被引用到的對(duì)象的復(fù)制叫做簡(jiǎn)間接復(fù)制。
    深度克隆要深入到多少層心傀,是一個(gè)不易確定的問(wèn)題屈暗。在決定以深度克隆的方式復(fù)制一個(gè)對(duì)象的時(shí)候,必須決定對(duì)間接復(fù)制的對(duì)象是采取淺度克隆還是繼續(xù)采用深度克隆脂男。因此养叛,在采用深度克隆時(shí),需要決定多深才算深宰翅。
    此外弃甥,在深度克隆的過(guò)程中,很可能會(huì)出現(xiàn)循環(huán)引用的問(wèn)題汁讼,必須小心處理淆攻。

使用序列化實(shí)現(xiàn)深度克隆

把對(duì)象寫(xiě)到流里的過(guò)程是序列化Serialization的過(guò)程;而把對(duì)象從流中讀出來(lái)的過(guò)程叫反序列化Deserialization過(guò)程嘿架。應(yīng)當(dāng)指出的是瓶珊,寫(xiě)到流里的是對(duì)象的一個(gè)拷貝,原對(duì)象仍然存在于JVM中眶明。

在Java語(yǔ)言里深度克隆一個(gè)對(duì)象艰毒,常常可以先使對(duì)象實(shí)現(xiàn)Serializable接口搜囱,然后把對(duì)象(實(shí)際上對(duì)象的拷貝)寫(xiě)到一個(gè)流里(序列化過(guò)程)丑瞧,再?gòu)牧骼镒x出來(lái)(反序列化過(guò)程)柑土,便可以重建對(duì)象。

public  Object deepClone() throws IOException, ClassNotFoundException{
    //將對(duì)象寫(xiě)到流里
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    oos.writeObject(this);
    //從流里讀回來(lái)
    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bis);
    return ois.readObject();
}

這樣做的前提就是對(duì)象及對(duì)象內(nèi)部所有引用到的對(duì)象都是可序列化的绊汹,否則稽屏,就需要仔細(xì)考察那些不可序列化的對(duì)象是否可以設(shè)置成transient,從而將至排除在復(fù)制過(guò)程之外西乖。

淺度克隆顯然比深度克隆更容易實(shí)現(xiàn)狐榔,因?yàn)镴ava語(yǔ)言的所有類(lèi)都會(huì)繼承一個(gè)clone()方法,而這個(gè)clone()方法所做的正是淺度克隆获雕。

有一些對(duì)象薄腻,比如線程Thread對(duì)象或者Socket對(duì)象,是不能簡(jiǎn)單復(fù)制或者共享的届案。不管是使用淺度克隆還是使用深度克隆庵楷,只要涉及這樣的間接對(duì)象,就必須把簡(jiǎn)介對(duì)象射程transient而不予復(fù)制楣颠;或者由程序自行創(chuàng)建出相當(dāng)?shù)耐葘?duì)象尽纽,權(quán)且當(dāng)做復(fù)制件使用。

孫大圣的身外身法術(shù)

孫大圣的身外身本領(lǐng)如果在Java語(yǔ)言里使用原型模式來(lái)實(shí)現(xiàn)的話童漩,會(huì)怎么樣呢弄贿?

首先,齊天大圣The Greatest Sage矫膨,即TheGreatestSage類(lèi)扮演客戶角色差凹。齊天大圣持有一個(gè)猢猻Monkey的實(shí)例,而猢猻就是大圣本尊豆拨。Monkey類(lèi)具有繼承自java.lang.Objectclone()方法直奋,因此,可以通過(guò)調(diào)用這個(gè)克隆方法來(lái)復(fù)制一個(gè)Monkey實(shí)例施禾。

示例代碼

大圣持有金箍棒的實(shí)例脚线,因此這里定義了一個(gè)金箍棒的類(lèi)GoldRingedStaff

/**
 * 金箍棒對(duì)象
 */
public class GoldRingedStaff {
    /**
     * 高度
     */
    private float height = 100.00f;
    /**
     * 半徑
     */
    private float radius = 10.00f;
    /**
     * 金箍棒變大方法
     */
    public void grow() {
        this.radius *= 2;
        this.height *= 2;
    }
    /**
     * 金箍棒縮小方法
     */
    public void shrink() {
        this.radius /= 2;
        this.height /= 2;
    }
}

大圣本尊使用Monkey類(lèi)來(lái)表示,這個(gè)類(lèi)來(lái)扮演具體的原型角色:

/**
 * 獼猴類(lèi)弥搞,大圣本尊由獼猴類(lèi)來(lái)表示
 */
public class Monkey implements Cloneable {
    /**
     * 身高
     */
    private int height;
    /**
     * 體重
     */
    private int weight;
    /**
     * 出生日期
     */
    private Date birthDay;
    /**
     * 金箍棒
     */
    private GoldRingedStaff staff;
    /**
     * 構(gòu)造函數(shù)邮绿,指定創(chuàng)建事件和給定金箍棒
     */
    public Monkey() {
        this.birthDay = new Date();
        this.staff = new GoldRingedStaff();
    }

    /**
     * 克隆方法,直接調(diào)用接口的克隆方法
     */
    public Object clone() {
        Monkey temp = null;
        try {
            temp = (Monkey)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        } finally {
            return temp;
        }
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getWeight() {
        return weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }

    public Date getBirthDay() {
        return birthDay;
    }

    public void setBirthDay(Date birthDay) {
        this.birthDay = birthDay;
    }

    public GoldRingedStaff getStaff() {
        return staff;
    }

    public void setStaff(GoldRingedStaff staff) {
        this.staff = staff;
    }
}

孫大圣本尊則使用類(lèi)TheGreatestSage來(lái)表示:

public class TheGreatestSage {
    private Monkey monkey = new Monkey();
    
    public void change() {
        Monkey copyMonkey = (Monkey) monkey.clone();
        System.out.println("大圣本尊的生日是:" + monkey.getBirthDay());
        System.out.println("克隆大圣的生日是:" + copyMonkey.getBirthDay());
        System.out.println("大圣本尊同克隆大圣是否為同一個(gè)對(duì)象:" + (monkey == copyMonkey));
        System.out.println("大圣本尊持有的金箍棒 同 克隆大圣持有的金箍棒是否為同一個(gè)對(duì)象:" + (monkey.getStaff() == copyMonkey.getStaff()));
    }
    
    public static void main(String[] args) {
        TheGreatestSage sage = new TheGreatestSage();
        sage.change();
    }
}

當(dāng)運(yùn)行TheGreatestSage類(lèi)時(shí)攀例,首先創(chuàng)建大圣本尊對(duì)象船逮,然后淺度克隆大圣本尊對(duì)象。程序在運(yùn)行時(shí)輸出的信息如下:

大圣本尊的生日是:Wed Jun 28 10:19:53 CST 2017
克隆大圣的生日是:Wed Jun 28 10:19:53 CST 2017
大圣本尊同克隆大圣是否為同一個(gè)對(duì)象:false
大圣本尊持有的金箍棒 同 克隆大圣持有的金箍棒是否為同一個(gè)對(duì)象:true

可以看出粤铭,首先挖胃,復(fù)制的大圣本尊具有和原始的大圣本尊一樣的birthDay,而本尊對(duì)象不相等,這表明他們二者是克隆關(guān)系酱鸭;其次吗垮,復(fù)制的大圣本尊持有的金箍棒和原始大圣持有的金箍棒為同一個(gè)對(duì)象,這表明二者所持有的金箍棒為同一根凹髓,而非兩根烁登。

正如前面所述,繼承自java.lang.Object類(lèi)的clone()方法是淺度克隆蔚舀。換而言之饵沧,齊天大圣所有化身所持有的金箍棒引用全都是指向一個(gè)對(duì)象的,這與《西游記》中描寫(xiě)并不一致赌躺。要想要糾正這一點(diǎn)狼牺,就需要考慮使用深度克隆

想要做到深度克隆,就要求所有需要復(fù)制的對(duì)象都實(shí)現(xiàn)java.io.Serializable接口寿谴。

實(shí)例代碼锁右,修改為深度克隆后

孫大圣的源代碼

public class TheGreatestSage {
    private Monkey monkey = new Monkey();
    
    public void change() {
        Monkey copyMonkey = null;
        try {
            copyMonkey = (Monkey) monkey.deepClone();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("大圣本尊的生日是:" + monkey.getBirthDay());
        System.out.println("克隆大圣的生日是:" + copyMonkey.getBirthDay());
        System.out.println("大圣本尊同克隆大圣是否為同一個(gè)對(duì)象:" + (monkey == copyMonkey));
        System.out.println("大圣本尊持有的金箍棒 同 克隆大圣持有的金箍棒是否為同一個(gè)對(duì)象:" + (monkey.getStaff() == copyMonkey.getStaff()));
    }
    
    public static void main(String[] args) {
        TheGreatestSage sage = new TheGreatestSage();
        sage.change();
    }
}

在大圣本尊Monkey類(lèi)中,原有一個(gè)克隆方法clone()讶泰,為淺度克隆,因此新增一個(gè)方法deepClone()拂到,為深度克隆痪署。在deepClone()方法中,大圣本尊對(duì)象(一個(gè)拷貝)被序列化兄旬,然后又被反序列化狼犯。反序列化后的對(duì)象也就成了一個(gè)深度克隆后的對(duì)象。deepClone()方法如下:

/**
 * 獼猴類(lèi)领铐,大圣本尊由獼猴類(lèi)來(lái)表示
 */
public class Monkey implements Cloneable,Serializable {

    //………………

    /**
     * 克隆方法悯森,直接調(diào)用接口的克隆方法
     */
    public Object clone() {
        Monkey temp = null;
        try {
            temp = (Monkey)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        } finally {
            return temp;
        }
    }
    
    public Object deepClone() throws IOException, ClassNotFoundException {
        //將對(duì)象寫(xiě)入流中
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(this);
        //將對(duì)象從流中讀取回來(lái)
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        return objectInputStream.readObject();
    }

    //……………………
}

對(duì)于金箍棒GoldRingedStaff類(lèi),也讓其實(shí)現(xiàn)java.io.Serializable接口:

public class GoldRingedStaff implements Serializable {

    //………………

}

修改后的代碼運(yùn)行結(jié)果為:

大圣本尊的生日是:Wed Jun 28 11:31:01 CST 2017
克隆大圣的生日是:Wed Jun 28 11:31:01 CST 2017
大圣本尊同克隆大圣是否為同一個(gè)對(duì)象:false
大圣本尊持有的金箍棒 同 克隆大圣持有的金箍棒是否為同一個(gè)對(duì)象:false

從運(yùn)行結(jié)果可以看出绪撵,大圣的金箍棒同克隆大圣的金箍棒是不同的對(duì)象瓢姻。這是因?yàn)槭褂昧?strong>深度克隆,從而將大圣本尊所引用的對(duì)象也都復(fù)制了一遍音诈,其中也包括金箍棒幻碱。

原型模式優(yōu)缺點(diǎn)總結(jié)

原型模式的優(yōu)點(diǎn)

原型模式允許在運(yùn)行時(shí)動(dòng)態(tài)的改變具體的實(shí)現(xiàn)類(lèi)型。原型模式可以在運(yùn)行期間细溅,有客戶來(lái)注冊(cè)符合原型接口的實(shí)現(xiàn)類(lèi)型褥傍,也可以動(dòng)態(tài)的改變具體的實(shí)現(xiàn)類(lèi)型,看起來(lái)接口沒(méi)有任何變化喇聊,但是其實(shí)運(yùn)行的已經(jīng)是另外一個(gè)類(lèi)實(shí)體了恍风。因?yàn)榭寺∫粋€(gè)原型對(duì)象就類(lèi)似于實(shí)例化一個(gè)類(lèi)。

原型模式的缺點(diǎn)

原型模式最主要的缺點(diǎn)是每一個(gè)類(lèi)都必須要配備一個(gè)克隆方法。配備克隆方法需要對(duì)類(lèi)的功能進(jìn)行通盤(pán)考慮朋贬,這對(duì)于全新的類(lèi)來(lái)說(shuō)并不是很難凯楔,但是對(duì)于已有的類(lèi)來(lái)說(shuō)并不容易,可別是當(dāng)一個(gè)類(lèi)引用不支持序列化的間接對(duì)象兄世,或者引用含有循環(huán)結(jié)構(gòu)的時(shí)候啼辣。

參考

《JAVA與模式》之原型模式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市御滩,隨后出現(xiàn)的幾起案子鸥拧,更是在濱河造成了極大的恐慌,老刑警劉巖削解,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件富弦,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡氛驮,警方通過(guò)查閱死者的電腦和手機(jī)腕柜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)矫废,“玉大人盏缤,你說(shuō)我怎么就攤上這事”推耍” “怎么了唉铜?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)律杠。 經(jīng)常有香客問(wèn)我潭流,道長(zhǎng),這世上最難降的妖魔是什么柜去? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任灰嫉,我火速辦了婚禮,結(jié)果婚禮上嗓奢,老公的妹妹穿的比我還像新娘讼撒。我一直安慰自己,他們只是感情好蔓罚,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布椿肩。 她就那樣靜靜地躺著,像睡著了一般豺谈。 火紅的嫁衣襯著肌膚如雪郑象。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天茬末,我揣著相機(jī)與錄音厂榛,去河邊找鬼盖矫。 笑死,一個(gè)胖子當(dāng)著我的面吹牛击奶,可吹牛的內(nèi)容都是我干的辈双。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼柜砾,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼湃望!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起痰驱,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤证芭,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后担映,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體废士,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年蝇完,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了官硝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡短蜕,死狀恐怖氢架,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情朋魔,我是刑警寧澤达箍,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站铺厨,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏硬纤。R本人自食惡果不足惜解滓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望筝家。 院中可真熱鬧洼裤,春花似錦、人聲如沸溪王。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)莹菱。三九已至移国,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間道伟,已是汗流浹背迹缀。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工使碾, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人祝懂。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓票摇,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親砚蓬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子矢门,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容