設計模式(十六)原型模式

相關文章
設計模式系列java

前言

公衆號有同窗留言設計模式,才發現很久沒有寫設計模式了。關於建立型設計模式只差原型模式沒寫了,這一篇就來填補這個空缺。
設計模式

1.原型模式定義

原型模式定義

定義:用原型實例指定建立對象的種類,並經過拷貝這些原型建立新的對象。數組

原型模式UML圖

在原型模式中有以下角色:微信

  • Client:客戶端角色。
  • Prototype:抽象原型角色,抽象類或者接口,用來聲明clone方法。
  • ConcretePrototype:具體的原型類,是客戶端角色使用的對象,即被複制的對象。

須要注意的是,Prototype一般是不用本身定義的,由於拷貝這個操做十分經常使用,Java中就提供了Cloneable接口來支持拷貝操做,它就是原型模式中的Prototype。固然,原型模式也未必非得去實現Cloneable接口,也有其餘的實現方式。ide

2.原型模式簡單實現

原型模式的核心是clone方法,經過該方法進行拷貝,這裏舉一個名片拷貝的例子。
如今已經流行電子名片了,只要掃一下就能夠將名片拷貝到本身的名片庫中, 咱們先實現名片類。函數

具體的原型類

public class BusinessCard implements Cloneable {
    private String name;
    private String company;
    public BusinessCard(){
        System.out.println("執行構造函數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);
        System.out.println("company:" + company);
    }
}複製代碼

BusinessCard類實現了Cloneable接口,它是一個標識接口,表示這個對象是可拷貝的,只要重寫clone方法就能夠實現拷貝。若是實現了Cloneable接口卻沒有重寫clone方法就會報錯。須要注意的是,clone方法不是在Cloneable接口中定義的(Cloneable接口中沒有定義任何方法),而是在Object中定義的。性能

客戶端調用

public class Client {
    public static void main(String[] args) {
        BusinessCard businessCard = new BusinessCard();
        businessCard.setName("錢三");
        businessCard.setCompany("阿里");
        //拷貝名片
        BusinessCard cloneCard1 = businessCard.clone();
        cloneCard1.setName("趙四");
        cloneCard1.setCompany("百度");

        BusinessCard cloneCard2 = businessCard.clone();
        cloneCard2.setName("孫五");
        cloneCard2.setCompany("騰訊");

        businessCard.show();
        cloneCard1.show();
        cloneCard2.show();
    }
}複製代碼

除了第一個名片,其餘兩個名片都是經過clone方法獲得的,須要注意的是,clone方法並不會執行cloneCard1和cloneCard2的構造函數,運行結果爲:
執行構造函數BusinessCard
name:錢三
company:阿里
name:趙四
company:百度
name:孫五
company:騰訊this

3.淺拷貝和深拷貝

原型模式涉及到淺拷貝和深拷貝的知識點,爲了更好的理解它們,還須要舉一些例子。spa

實現淺拷貝

上述的例子中,BusinessCard的字段都是String類型的,若是字段是引用的類型的,會出現什麼狀況呢?以下所示。設計

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

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

    public void setCompany(String name, String address) {
        this.company.setName(name);
        this.company.setAddress(address);
    }

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

    public void show() {
        System.out.println("name:" + name);
        System.out.println("company:" + company.getName() + "-address-" + company.getAddress());
    }

}複製代碼

咱們定義了DeepBusinessCard 類,它的字段company是引用類型的,Company類以下所示。

public class Company {
    private String name;
    private String address;

    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }複製代碼

在客戶端使用DeepBusinessCard:

public class Client {
    public static void main(String[] args) {
       DeepBusinessCard businessCard=new DeepBusinessCard();
        businessCard.setName("錢三");
        businessCard.setCompany("阿里","北京望京");

        DeepBusinessCard cloneCard1=businessCard.clone();
        cloneCard1.setName("趙四");
        cloneCard1.setCompany("百度","北京西二旗");

        DeepBusinessCard cloneCard2=businessCard.clone();
        cloneCard2.setName("孫五");
        cloneCard2.setCompany("騰訊","北京中關村");

        businessCard.show();
        cloneCard1.show();
        cloneCard2.show();
    }
}複製代碼

運行結果爲:
name:錢三
company:騰訊-address-北京中關村
name:趙四
company:騰訊-address-北京中關村
name:孫五
company:騰訊-address-北京中關村

從結果能夠看出company字段爲最後設置的"騰訊"、"北京中關村"。這是由於Object類提供的clone方法,不會拷貝對象中的內部數組和引用對象,致使它們仍舊指向原來對象的內部元素地址,這種拷貝叫作淺拷貝。
company字段是引用類型,businessCard被拷貝後,company字段仍舊指向原來的businessCard對象的company字段的地址。這樣咱們每次設置company字段,都會覆蓋上一次設置的值,最終留下的就是最後一次設置的值:"騰訊"、"北京中關村"。
引用關係以下圖所示。


這樣的引用關係顯然不符合需求,有多個對象能夠修改company,咱們應該將引用關係改成以下形式:


拷貝businessCard對象的同時,也將它內部的引用對象company進行拷貝,使得每一個拷貝的對象之間無任何關聯,都指向了自身對應的company,這種拷貝就是深拷貝。

實現深拷貝

首先須要修改Company類,以下所示。

public class Company implements Cloneable{
    private String name;
    private String address;
    ...
    public Company clone(){
        Company company=null;
        try {
            company= (Company) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return company;
    }
}複製代碼

爲了實現Company類能被拷貝,Company類也須要實現Cloneable接口而且覆寫clone方法。接着修改DeepBusinessCard的clone方法:

public class DeepBusinessCard implements Cloneable {
    private String name;
    private Company company = new Company();
    ...
    @Override
    public DeepBusinessCard clone() {
        DeepBusinessCard businessCard = null;
        try {
            businessCard = (DeepBusinessCard) super.clone();
            businessCard.company = this.company.clone();//1

        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return businessCard;
    }
  ...
}複製代碼

在註釋1處增長了對company字段的拷貝處理。最後在客戶端調用,輸出的結果爲:
name:錢三
company:阿里-address-北京望京
name:趙四
company:百度-address-北京西二旗
name:孫五
company:騰訊-address-北京中關村

4.原型模式的使用場景

  • 若是類的初始化須要耗費較多的資源,那麼能夠經過原型拷貝避免這些消耗。
  • 經過new產生一個對象須要很是繁瑣的數據準備或訪問權限,則可使用原型模式。
  • 一個對象須要提供給其餘對象訪問,並且各個調用者可能都須要修改其值時,能夠拷貝多個對象供調用者使用,即保護性拷貝。

5.原型模式的優缺點

優勢

原型模式是在內存中二進制流的拷貝,要比new一個對象的性能要好,特別是須要產生大量對象時。

缺點

直接在內存中拷貝,構造函數是不會執行的,這樣就減小了約束,這既是優勢也是缺點,須要在實際應用中去考量。

參考資料
《大話設計模式》
《設計模式之禪》
《Android源碼設計模式解析與實戰》


歡迎關注個人微信公衆號,第一時間得到博客更新提醒,以及更多成體系的Android相關原創技術乾貨。
掃一掃下方二維碼或者長按識別二維碼,便可關注。

相關文章
相關標籤/搜索