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

原型模式介紹

原型模式(Prototype Pattern)是創(chuàng)建型模式的一種。這種模式是實(shí)現(xiàn)了一個(gè)原型接口,該接口用于創(chuàng)建當(dāng)前對(duì)象的克隆。當(dāng)直接創(chuàng)建對(duì)象的代價(jià)比較大時(shí),則采用這種模式。

原型模式定義

用原型實(shí)例指定創(chuàng)建對(duì)象的種類,并通過(guò)拷貝這些原型創(chuàng)建新的對(duì)象。

原型模式使用場(chǎng)景

  1. 如果類的初始化需要耗費(fèi)較多的資源,那么可以通過(guò)原型拷貝避免這些消耗。
  2. 通過(guò)new產(chǎn)生一個(gè)對(duì)象需要非常繁瑣的數(shù)據(jù)準(zhǔn)備或訪問(wèn)權(quán)限,則可以使用原型模式。
  3. 一個(gè)對(duì)象需要提供給其他對(duì)象訪問(wèn),而且各個(gè)調(diào)用者可能都需要修改其值時(shí),可以拷貝多個(gè)對(duì)象供調(diào)用者使用,即保護(hù)性拷貝。

原型模式 UML 類圖


角色介紹:

  • Client:客戶端角色。
  • Prototype:抽象原型角色,抽象類或者接口,用來(lái)聲明clone方法。
  • ConcretePrototype:具體的原型類,是客戶端角色使用的對(duì)象,即被復(fù)制的對(duì)象。

需要注意的是,Prototype 通常是不用自己定義的,因?yàn)榭截愡@個(gè)操作十分常用,Java 中就提供了Cloneable 接口來(lái)支持拷貝操作,它就是原型模式中的Prototype。當(dāng)然,原型模式也未必非得去實(shí)現(xiàn)Cloneable接口,也有其他的實(shí)現(xiàn)方式。

原型模式的實(shí)現(xiàn)

原型模式的核心是clone方法,通過(guò)該方法進(jìn)行拷貝,這里舉一個(gè)名片拷貝的例子。
現(xiàn)在已經(jīng)流行電子名片了,只要掃一下就可以將名片拷貝到自己的名片庫(kù)中, 我們先實(shí)現(xiàn)名片類。
Java 原型類就是 Cloneable 了

具體原型類

public class BusinessCard {
    private String name;
    private String company;

    public BusinessCard() {
        System.out.println("執(zhí)行構(gòu)造函數(shù)BusinessCard");
    }

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

    public void setCompany(String company) {
        this.company = company;
    }

    @Override
    public BusinessCard clone() {
        BusinessCard businessCard = null;
        try {
            businessCard = (BusinessCard) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return businessCard;
    }

    public void show() {
        System.out.println("name:" + name + ", company:" + company);
    }
}

注意: Cloneable 是一個(gè)空接口,clone 方法是在 Object 類中定義,訪問(wèn)類型為 protected,當(dāng)執(zhí)行 clone 方法時(shí)會(huì)檢測(cè)是不是 Cloneable 類型,如果不是就會(huì)報(bào)java.lang.CloneNotSupportedException 錯(cuò)誤。

客戶端

public class Client {
    public static void main(String[] args) {
        BusinessCard businessCard = new BusinessCard();
        businessCard.setName("小明");
        businessCard.setCompany("百度");
  
        BusinessCard cloneCard1 = businessCard.clone();
        cloneCard1.setName("小強(qiáng)");
        cloneCard1.setCompany("阿里");

        BusinessCard cloneCard2 = businessCard.clone();
        cloneCard2.setName("小紅");
        cloneCard2.setCompany("騰訊");

        businessCard.show();
        cloneCard1.show();
        cloneCard2.show();
    }
}

除了第一個(gè)名片,其他兩個(gè)名片都是通過(guò)clone方法得到的,需要注意的是,clone方法返回的對(duì)象是在內(nèi)存中二進(jìn)制拷貝的原對(duì)象,并不會(huì)執(zhí)行其構(gòu)造方法。運(yùn)行結(jié)果為:

執(zhí)行構(gòu)造函數(shù)BusinessCard
name:小明, company:百度
name:小強(qiáng), company:阿里
name:小紅, company:騰訊

淺拷貝和深拷貝

原型模式涉及到淺拷貝和深拷貝的知識(shí)點(diǎn),為了更好的理解它們,還需要舉一些例子。

實(shí)現(xiàn)淺拷貝
上述的例子中,BusinessCard 的字段都是String類型的,如果字段是引用的類型的,會(huì)出現(xiàn)什么情況呢?如下所示:

public class BusinessCard implements Cloneable {
    private String name;
    private Company company = new Company();

    public BusinessCard() {
        System.out.println("執(zhí)行構(gòu)造函數(shù)BusinessCard");
    }

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

    public void setCompany(String name, String address) {
        company.name = name;
        company.address = address;
    }

    @Override
    public BusinessCard clone() {
        BusinessCard businessCard = null;
        try {
            businessCard = (BusinessCard) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return businessCard;
    }

    public void show() {
        System.out.println("name:" + name + ", company:" + company);
    }
}
public class Company {
    String name;
    String address;

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

客戶端代碼

public class Client {
    public static void main(String[] args) {
        BusinessCard businessCard = new BusinessCard();
        businessCard.setName("小明");
        businessCard.setCompany("百度", "北京");

        BusinessCard cloneCard1 = businessCard.clone();
        cloneCard1.setName("小強(qiáng)");
        cloneCard1.setCompany("阿里", "杭州");

        BusinessCard cloneCard2 = businessCard.clone();
        cloneCard2.setName("小紅");
        cloneCard2.setCompany("騰訊", "深圳");

        businessCard.show();
        cloneCard1.show();
        cloneCard2.show();
    }
}

執(zhí)行結(jié)果:

執(zhí)行構(gòu)造函數(shù)BusinessCard
name:小明, company:Company{name='騰訊', address='深圳'}
name:小強(qiáng), company:Company{name='騰訊', address='深圳'}
name:小紅, company:Company{name='騰訊', address='深圳'}

從結(jié)果可以看出 company 字段為最后設(shè)置的”騰訊”、”深圳”。這是因?yàn)?Object 類提供的 clone 方法,不會(huì)執(zhí)行對(duì)象中的內(nèi)部數(shù)組和引用對(duì)象的 clone 方法,導(dǎo)致 clone 出的對(duì)象的 company 指向的對(duì)象仍舊是原型中的 company 指向的對(duì)象。這種拷貝叫做淺拷貝。

淺拷貝顯然不符號(hào)要求,實(shí)際開(kāi)發(fā)時(shí)我們希望每個(gè)對(duì)象可以獨(dú)立變化,此時(shí)我們就需要通過(guò)深拷貝來(lái)解決,也就是執(zhí)行 clone 方法時(shí),也需要執(zhí)行內(nèi)部對(duì)象的 clone 方法。

實(shí)現(xiàn)深拷貝
首先需要讓 Company 支持 clone,修改代碼如下:

public class Company implements Cloneable {
    String name;
    String address;

    @Override
    public Company clone() {
        Company company = null;
        try {
            company = (Company) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return company;
    }

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

修改 BusinessCard 的 clone 方法

public class BusinessCard implements Cloneable {
  ...
  @Override
    public BusinessCard clone() {
        BusinessCard businessCard = null;
        try {
            businessCard = (BusinessCard) super.clone();
            businessCard.company = company.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return businessCard;
    }
}

Client 不變,運(yùn)行結(jié)果如下:

執(zhí)行構(gòu)造函數(shù)BusinessCard
name:小明, company:Company{name='百度', address='北京'}
name:小強(qiáng), company:Company{name='阿里', address='杭州'}
name:小紅, company:Company{name='騰訊', address='深圳'}

總結(jié)

原型模式比較簡(jiǎn)單,核心問(wèn)題就是對(duì)原始對(duì)象進(jìn)行拷貝,需要注意的就是深、淺拷貝的問(wèn)題。開(kāi)發(fā)中,為了避免操作副本時(shí)影響原始對(duì)象,應(yīng)盡量采用深拷貝。

優(yōu)點(diǎn)
原型模式是在內(nèi)存中二進(jìn)制流的拷貝,要比 new 一個(gè)對(duì)象的性能要好,特別是需要產(chǎn)生大量對(duì)象時(shí)。
缺點(diǎn)
直接在內(nèi)存中拷貝,構(gòu)造函數(shù)是不會(huì)執(zhí)行的,這樣就減少了約束,這既是優(yōu)點(diǎn)也是缺點(diǎn),需要在實(shí)際應(yīng)用中去考量。

Android 源碼中的原型模式

Android 源碼中的常用的 Intent 就實(shí)現(xiàn)了 clone 方法。

public class Intent implements Parcelable, Cloneable {
    public Intent(Intent o) {
        this(o, COPY_MODE_ALL);
    }

    private Intent(Intent o, @CopyMode int copyMode) {
        this.mAction = o.mAction;
        this.mData = o.mData;
        this.mType = o.mType;
        this.mPackage = o.mPackage;
        this.mComponent = o.mComponent;

        if (o.mCategories != null) {
            this.mCategories = new ArraySet<>(o.mCategories);
        }

        if (copyMode != COPY_MODE_FILTER) {
            this.mFlags = o.mFlags;
            this.mContentUserHint = o.mContentUserHint;
            this.mLaunchToken = o.mLaunchToken;
            if (o.mSourceBounds != null) {
                this.mSourceBounds = new Rect(o.mSourceBounds);
            }
            if (o.mSelector != null) {
                this.mSelector = new Intent(o.mSelector);
            }

            if (copyMode != COPY_MODE_HISTORY) {
                if (o.mExtras != null) {
                    this.mExtras = new Bundle(o.mExtras);
                }
                if (o.mClipData != null) {
                    this.mClipData = new ClipData(o.mClipData);
                }
            } else {
                if (o.mExtras != null && !o.mExtras.maybeIsEmpty()) {
                    this.mExtras = Bundle.STRIPPED;
                }
            }
        }
    }    
    @Override
    public Object clone() {
        return new Intent(this);
    }
    ...
}

可以看到 clone 方法實(shí)際上內(nèi)部沒(méi)有調(diào)用 super.clone() 方法來(lái)實(shí)現(xiàn)對(duì)象拷貝,而是調(diào)用了 new Intent(this)??梢钥吹?Intent 中有很多參數(shù),使用 clone 即可增加構(gòu)建對(duì)象的便捷性,在 clone 中使用 new,可以實(shí)現(xiàn)定制化拷貝對(duì)象,如果使用 super.clone() 則會(huì)將原對(duì)象進(jìn)行拷貝。
使用 new 和 clone 需要根據(jù)構(gòu)造對(duì)象的成本來(lái)決定,如果對(duì)象的構(gòu)造成本比較高或者比較麻煩,那么使用 clone 方法效率更高,否則就使用 new。

最后編輯于
?著作權(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ù)。

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