設(shè)計(jì)模式04_原型模式_Prototype

1. 簡(jiǎn)介

原型模式是一種簡(jiǎn)單、易使用的創(chuàng)建型設(shè)計(jì)模式,通過(guò)給出一個(gè)原型對(duì)象來(lái)指明所創(chuàng)建的對(duì)象的類型,然后用復(fù)制這個(gè)原型對(duì)象的辦法創(chuàng)建出更多同類型的對(duì)象。原型模式要求對(duì)象實(shí)現(xiàn)一個(gè)可以“克隆”自身的接口,這樣就可以通過(guò)復(fù)制一個(gè)實(shí)例對(duì)象本身來(lái)創(chuàng)建一個(gè)新的實(shí)例。這樣一來(lái),通過(guò)原型實(shí)例創(chuàng)建新的對(duì)象,就不再需要關(guān)心這個(gè)實(shí)例本身的類型,只要實(shí)現(xiàn)了克隆自身的方法,就可以通過(guò)這個(gè)方法來(lái)獲取新的對(duì)象,而無(wú)須再去通過(guò)new來(lái)創(chuàng)建。

2.UML

protyotype

(1)客戶角色:客戶類提出創(chuàng)建對(duì)象的請(qǐng)求。
(2)抽象原型角色:這是一個(gè)抽象角色,通常由一個(gè)java接口或抽象類實(shí)現(xiàn),具體原型都需要實(shí)現(xiàn)該接口。
(3)具體原型角色:客戶端所需要的被復(fù)制的對(duì)象。

我們都知道Object是祖宗,所有的Java類都繼承至Object,而Object類提供了一個(gè)clone()方法,該方法可以將一個(gè)java對(duì)象復(fù)制一份,因此在java中可以直接使用clone()方法來(lái)復(fù)制一個(gè)對(duì)象。但是需要實(shí)現(xiàn)clone的Java類必須要實(shí)現(xiàn)一個(gè)接口:Cloneable.該接口表示該類能夠復(fù)制且具體復(fù)制的能力,如果不實(shí)現(xiàn)該接口而直接調(diào)用clone()方法會(huì)拋出CloneNotSupportedException異常。

Java中任何實(shí)現(xiàn)了Cloneable接口的類都可以通過(guò)調(diào)用clone()方法來(lái)復(fù)制一份自身然后傳給調(diào)用者。一般而言,clone()方法滿足:
(1) 對(duì)任何的對(duì)象x,都有x.clone() !=x,即克隆對(duì)象與原對(duì)象不是同一個(gè)對(duì)象。
(2) 對(duì)任何的對(duì)象x,都有x.clone().getClass()==x.getClass(),即克隆對(duì)象與原對(duì)象的類型一樣。
(3) 如果對(duì)象x的equals()方法定義恰當(dāng),那么x.clone().equals(x)應(yīng)該成立。

3. 示例

抽象原型角色

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

具體原型角色

public class ConcretePrototype1 implements Prototype {
    private String name;
    public Prototype clone(){
        ConcretePrototype1 prototype = new ConcretePrototype1();
        prototype.setName(this.name);
        return prototype;
    }
    public String toString(){
        return "Now in Prototype1 , name = " + this.name;
    }
    @Override
    public String getName() {
        return name;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }
}
public class ConcretePrototype2 implements Prototype {
    private String name;
    public Prototype clone(){
        ConcretePrototype2 prototype = new ConcretePrototype2();
        prototype.setName(this.name);
        return prototype;
    }
    public String toString(){
        return "Now in Prototype2 , name = " + this.name;
    }
    @Override
    public String getName() {
        return name;
    }

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

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

public class PrototypeManager {
    /**
     * 用來(lái)記錄原型的編號(hào)和原型實(shí)例的對(duì)應(yīng)關(guān)系
     */
    private static Map<String,Prototype> map = new HashMap<String,Prototype>();
    /**
     * 私有化構(gòu)造方法,避免外部創(chuàng)建實(shí)例
     */
    private PrototypeManager(){}
    /**
     * 向原型管理器里面添加或是修改某個(gè)原型注冊(cè)
     * @param prototypeId 原型編號(hào)
     * @param prototype    原型實(shí)例
     */
    public synchronized static void setPrototype(String prototypeId , Prototype prototype){
        map.put(prototypeId, prototype);
    }
    /**
     * 從原型管理器里面刪除某個(gè)原型注冊(cè)
     * @param prototypeId 原型編號(hào)
     */
    public synchronized static void removePrototype(String prototypeId){
        map.remove(prototypeId);
    }
    /**
     * 獲取某個(gè)原型編號(hào)對(duì)應(yīng)的原型實(shí)例
     * @param prototypeId    原型編號(hào)
     * @return    原型編號(hào)對(duì)應(yīng)的原型實(shí)例
     * @throws Exception    如果原型編號(hào)對(duì)應(yīng)的實(shí)例不存在,則拋出異常
     */
    public synchronized static Prototype getPrototype(String prototypeId) throws Exception{
        Prototype prototype = map.get(prototypeId);
        if(prototype == null){
            throw new Exception("您希望獲取的原型還沒(méi)有注冊(cè)或已被銷毀");
        }
        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);
            //有人注銷了這個(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();
        }
    }
}

4.深拷貝與淺拷貝

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

淺度克隆
  只負(fù)責(zé)克隆按值傳遞的數(shù)據(jù)(比如基本數(shù)據(jù)類型、String類型),而不復(fù)制它所引用的對(duì)象,換言之,所有的對(duì)其他對(duì)象的引用都仍然指向原來(lái)的對(duì)象。

深度克隆
  除了淺度克隆要克隆的值外,還負(fù)責(zé)克隆引用類型的數(shù)據(jù)。那些引用其他對(duì)象的變量將指向被復(fù)制過(guò)的新對(duì)象,而不再是原有的那些被引用的對(duì)象。換言之,深度克隆把要復(fù)制的對(duì)象所引用的對(duì)象都復(fù)制了一遍,而這種對(duì)被引用到的對(duì)象的復(fù)制叫做間接復(fù)制。深度克隆要深入到多少層,是一個(gè)不易確定的問(wèn)題。在決定以深度克隆的方式復(fù)制一個(gè)對(duì)象的時(shí)候,必須決定對(duì)間接復(fù)制的對(duì)象時(shí)采取淺度克隆還是繼續(xù)采用深度克隆。因此,在采取深度克隆時(shí),需要決定多深才算深。此外,在深度克隆的過(guò)程中,很可能會(huì)出現(xiàn)循環(huán)引用的問(wèn)題,必須小心處理。

image.png

另外,可能會(huì)有疑惑,這樣一種原型模式相比賦值構(gòu)造函數(shù)有什么優(yōu)勢(shì)呢?

Base b = new Derived();

現(xiàn)在要克隆b怎么辦?
只有Derived(b),但是如果只有抽象基類的調(diào)用接口(Base) 則你根本不知道具體要實(shí)例化哪個(gè)類(Derived),而且你也不需要知道,所以根本無(wú)法克隆.這時(shí)原形模式就顯現(xiàn)優(yōu)勢(shì)了 即只要調(diào)用clone 函數(shù)即可.

5. 優(yōu)缺點(diǎn)

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

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容