Java設計模式-原型模式

原型模式概述

使用原型模式時,咱們須要首先建立一個原型對象,再經過複製這個原型對象來建立更多類型的對象。原型對象能夠經過調用原型類中的克隆方法來克隆自身從而建立更多的對象。java

原型類的核心是如何實現克隆方法,其中有兩種經常使用的實現方法。編程

<!-- more -->網絡

原型模式結構

一、Prototype(抽象原型類):它是聲明克隆方法的接口,是全部具體原型類的公共父類,能夠是抽象類也能夠是接口,甚至還能夠是具體實現類。ide

二、ConcretePrototype(具體原型類):它實如今抽象原型類中聲明的克隆方法,在克隆方法中返回本身的一個克隆對象。函數

三、Client(客戶類):讓一個原型對象克隆自身從而建立一個新的對象,在客戶類中只須要直接實例化或經過工廠方法等方式建立一個原型對象,再經過調用該對象的克隆方法便可獲得多個相同的對象。因爲客戶類針對抽象原型類Prototype編程,所以用戶能夠根據須要選擇具體原型類,系統具備較好的可擴展性,增長或更換具體原型類都很方便。this

原型模式代碼

Java 中的 Object 類提供了一個 clone() 方法,能夠將一個 Java 對象複製一份。所以 Java 中能夠直接利用 Object 中的 clone() 方法克隆一份對象,但必需要實現 Cloneable 接口,表示本身能夠被複制。spa

一、抽象原型類即爲 Object 類,由於 Object 類中聲明瞭 clone() 方法了,子類實現便可。prototype

二、具體原型類代碼設計

/**
 * Author: YiFan
 * Date: 2018/12/12 09:53
 * Description: 具體原型類-筆記本
 */
public class NoteBook implements Cloneable {

    // 筆記本ID
    private int id;

    // 筆記本標題
    private String title;
    
    // 筆記本模型
    private String model;
    
    // 筆記本聯繫人-引用數據類型
    private Contact contact;

    public int getId() {
        return id;
    }

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

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }
    
    public void setContact(Contact contact) {
        this.contact = contact;
    }

    public Contact getContact() {
        return contact;
    }

    @Override
    public String toString() {
        return "筆記本ID:" + id + ",筆記本標題:" + title + ", 筆記本模型:" + model;
    }

    // 重寫抽象原型類Object中的clone()方法-克隆NoteBook對象
    @Override
    public NoteBook clone() throws CloneNotSupportedException {
        Object object;
        object = super.clone();
        return (NoteBook) object;
    }
}

// 筆記本的聯繫人
class Contact {

    private int num;

    public void setNum(int num) {
        this.num = num;
    }

    public int getNum() {
        return num;
    }
}

三、客戶端代碼code

/**
 * Author: YiFan
 * Date: 2018/12/12 10:02
 * Description: 客戶端
 */
public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {

        // 建立原型對象
        NoteBook noteBook1 = new NoteBook();

        // 建立contact對象
        Contact contact = new Contact();

        // 設置contact的num屬性
        contact.setNum(1);

        // 設置noteBook1對象的屬性
        noteBook1.setId(1);
        noteBook1.setTitle("2017");
        noteBook1.setModel("22x22");

        System.out.println(noteBook1);

        // 將聯繫人contact添加到notebook1中
        noteBook1.setContact(contact);

        // 建立noteBook2引用變量
        NoteBook noteBook2;

        // 克隆noteBook1對象
        noteBook2 = noteBook1.clone();

        // 判斷noteBook1和noteBook2指向的是否爲同一個對象
        System.out.println("noteBook1和noteBook2指向的是否爲同一個對象:"
                + (noteBook1 == noteBook2));

        // 設置noteBook2對象的屬性
        noteBook2.setId(2);
        noteBook2.setTitle("2018");

        System.out.println(noteBook2);

        // 判斷noteBook1和noteBook2對象中的contact對象是不是同一個對象
        System.out.println("noteBook1和noteBook2對象中的contact對象是不是同一個對象:"
                + (noteBook1.getContact() == noteBook2.getContact()));

        // noteBook2對象將contact中的num設置爲2
        noteBook2.getContact().setNum(2);

        // 打印noteBook1對象中contact對象的num值
        System.out.println(noteBook1.getContact().getNum());
    }
}

執行結果爲:

筆記本ID:1,筆記本標題:2017, 筆記本模型:22x22
noteBook1和noteBook2指向的是否爲同一個對象:false
筆記本ID:2,筆記本標題:2018, 筆記本模型:22x22
noteBook1和noteBook2對象中的contact對象是不是同一個對象:true
2

執行結果的最後一行爲 true 表示 noteBook1 和 noteBook2 對象中的 contact 對象是同一個對象,也就代表 noteBook1 對象並無將本身的 contact 變量拷貝給 noteBook2,只是拷貝 contact 對象的引用給 noteBook2。noteBook2 設置 contact 對象中的 num 值爲 2 後 noteBook1.getContact().getNum() 爲 2 了,代表兩個對象中的 contact 是同一個對象。

淺克隆和深克隆

淺克隆

淺克隆就如同上述的代碼同樣,若是原型對象的成員變量是值類型,將複製一份給克隆對象;若是原型對象的成員變量是引用類型,則將引用對象的地址複製一份給克隆對象,也就是說原型對象和克隆對象的成員變量指向相同的內存地址。簡單來講,在淺克隆中,當對象被複制時只複製它自己和其中包含的值類型的成員變量,而引用類型的成員對象並無複製。

深克隆

在深克隆中,不管原型對象的成員變量是值類型仍是引用類型,都將複製一份給克隆對象,深克隆將原型對象的全部引用對象也複製一份給克隆對象。簡單來講,在深克隆中,除了對象自己被複制外,對象所包含的全部成員變量也將複製。

在Java語言中,若是須要實現深克隆,能夠經過序列化(Serialization)等方式來實現。序列化就是將對象寫到流的過程,寫到流中的對象是原有對象的一個拷貝,而原對象仍然存在於內存中。經過序列化實現的拷貝不只能夠複製對象自己,並且能夠複製其引用的成員對象,所以經過序列化將對象寫到一個流中,再從流裏將其讀出來,能夠實現深克隆。須要注意的是可以實現序列化的對象其類必須實現 Serializable 接口,不然沒法實現序列化操做。

深克隆代碼

具體原型類代碼

/**
 * Author: YiFan
 * Date: 2018/12/12 09:53
 * Description: 具體原型類-筆記本
 */

// 實現Serializable接口表示該類能夠被序列化
public class NoteBook implements Serializable {

    // 筆記本ID
    private int id;

    // 筆記本標題
    private String title;

    // 筆記本模型
    private String model;

    // 筆記本聯繫人-引用數據類型
    private Contact contact;

    public int getId() {
        return id;
    }

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

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public void setContact(Contact contact) {
        this.contact = contact;
    }

    public Contact getContact() {
        return contact;
    }

    @Override
    public String toString() {
        return "筆記本ID:" + id + ",筆記本標題:" + title + ", 筆記本模型:" + model;
    }

    // 使用序列化技術進行深克隆
    public NoteBook deepClone() throws IOException, ClassNotFoundException {
        // 將對象寫入流中
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(this);

        // 將對象從流中取出
        ByteArrayInputStream byteArrayInputStream = new
                ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);

        return (NoteBook) objectInputStream.readObject();
    }
}

// 筆記本的聯繫人
class Contact implements Serializable {

    private int num;

    public void setNum(int num) {
        this.num = num;
    }

    public int getNum() {
        return num;
    }
}

客戶端代碼

/**
 * Author: YiFan
 * Date: 2018/12/12 10:02
 * Description: 客戶端
 */
public class Client {

    public static void main(String[] args) throws IOException, ClassNotFoundException {

        // 建立原型對象
        NoteBook noteBook1 = new NoteBook();

        // 建立contact對象
        Contact contact = new Contact();

        // 設置contact的num屬性
        contact.setNum(1);

        // 設置noteBook1對象的屬性
        noteBook1.setId(1);
        noteBook1.setTitle("2017");
        noteBook1.setModel("22x22");

        System.out.println(noteBook1);

        // 將聯繫人contact添加到notebook1中
        noteBook1.setContact(contact);

        // 建立noteBook2引用變量
        NoteBook noteBook2;

        // 克隆noteBook1對象
        // noteBook2 = noteBook1.clone();
        
        // 調用deepClone()方法進行深克隆
        noteBook2 = noteBook1.deepClone();

        // 判斷noteBook1和noteBook2指向的是否爲同一個對象
        System.out.println("noteBook1和noteBook2指向的是否爲同一個對象:"
                + (noteBook1 == noteBook2));

        // 設置noteBook2對象的屬性
        noteBook2.setId(2);
        noteBook2.setTitle("2018");

        System.out.println(noteBook2);

        // 判斷noteBook1和noteBook2對象中的contact對象是不是同一個對象
        System.out.println("noteBook1和noteBook2對象中的contact對象是不是同一個對象:"
                + (noteBook1.getContact() == noteBook2.getContact()));

        // noteBook2對象將contact中的num設置爲2
        noteBook2.getContact().setNum(2);

        // 打印noteBook1對象中contact對象的num值
        System.out.println(noteBook1.getContact().getNum());
    }
}

客戶端代碼只是將 noteBook2 = noteBook1.clone() 代碼改爲 noteBook2 = noteBook1.deepClone()。執行結果:

筆記本ID:1,筆記本標題:2017, 筆記本模型:22x22
noteBook1和noteBook2指向的是否爲同一個對象:false
筆記本ID:2,筆記本標題:2018, 筆記本模型:22x22
noteBook1和noteBook2對象中的contact對象是不是同一個對象:false
1

能夠看到這條語句: noteBook1 和 noteBook2 對象中的 contact 對象是不是同一個對象:false。表示 noteBook1 將值類型成員變量和引用類型成員變量都複製到了 noteBook2 中,進行了深克隆。

原型管理器的引入和實現

原型管理器是將多個原型對象存儲在一個集合中供客戶端使用,它是一個專門負責克隆對象的工廠,其中定義了一個集合用於存儲原型對象,若是須要某個原型對象的一個克隆,能夠經過複製集合中對應的原型對象來得到。在原型管理器中針對抽象原型類進行編程,以便擴展。

下面模擬一個簡單的戴爾筆記本的設計與實現:戴爾公司須要生產不少型號的筆記本,例如外星人、XPS、laptop等,爲了提升效率,已經爲各型號建立了模板了,也就是搭建好了模型,生產廠能夠經過這些模型快速生產新的筆記本,這些模板要統一管理,生產廠根據需求生產不一樣的新筆記本。如需 XPS 筆記本則調用 XPS 型號的模板生產便可。

原型管理器實現代碼
/**
 * Author: YiFan
 * Date: 2018/12/12 19:52
 * Description: 抽象接口,提供clone()方法的實現
 */
public interface DellNoteBook extends Cloneable {

    DellNoteBook clone() throws CloneNotSupportedException;
    void desc();
}

/**
 * Author: YiFan
 * Date: 2018/12/12 19:56
 * Description: 外星人筆記本
 */
public class AlienNoteBook implements DellNoteBook {

    @Override
    public DellNoteBook clone() throws CloneNotSupportedException {
        Object object;
        object = super.clone();
        return (DellNoteBook) object;
    }

    @Override
    public void desc() {
        System.out.println("外星人筆記本");
    }
}

/**
 * Author: YiFan
 * Date: 2018/12/12 19:58
 * Description: XPS筆記本
 */
public class XPSNoteBook implements DellNoteBook {

    @Override
    public DellNoteBook clone() throws CloneNotSupportedException {
        Object object;
        object = super.clone();
        return (DellNoteBook) object;
    }

    @Override
    public void desc() {
        System.out.println("XPS筆記本");
    }
}

/**
 * Author: YiFan
 * Date: 2018/12/12 20:14
 * Description: 原型管理器(使用餓漢式單例實現)
 */
public class PrototypeManager {

    private HashMap<String, DellNoteBook> ht = new HashMap<>();
    private static PrototypeManager pm = new PrototypeManager();

    // 爲HashTable增長筆記本對象
    private PrototypeManager() {
        ht.put("alien", new AlienNoteBook());
        ht.put("xps", new XPSNoteBook());
    }

    // 增長新的筆記本
    public void addDellNoteBook(String key, DellNoteBook dellNoteBook) {
        ht.put(key, dellNoteBook);
    }

    public DellNoteBook getDellNoteBook(String key) throws CloneNotSupportedException {
        return ht.get(key).clone();
    }

    public static PrototypeManager getInstance() {
        return pm;
    }
}

/**
 * Author: YiFan
 * Date: 2018/12/12 20:21
 * Description: 客戶端
 */
public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {

        // 獲取原型管理器對象,只須要惟一一個,使用單例模式(餓漢式)建立
        PrototypeManager prototypeManager = PrototypeManager.getInstance();

        DellNoteBook book1, book2, book3, book4;

        book1 = prototypeManager.getDellNoteBook("alien");
        book1.desc();

        book2 = prototypeManager.getDellNoteBook("alien");
        book2.desc();

        System.out.println(book1 == book2);

        book3 = prototypeManager.getDellNoteBook("xps");
        book3.desc();

        book4 = prototypeManager.getDellNoteBook("xps");
        book4.desc();

        System.out.println(book3 == book4);
    }
}

執行結果:

外星人筆記本
外星人筆記本
false
XPS筆記本
XPS筆記本
false

原型模式總結

原型模式做爲一種快速建立大量相同或類似對象的方式,在軟件開發中應用較爲普遍,不少軟件提供的複製和粘貼操做就是原型模式的典型應用,下面對該模式的使用效果和適用狀況進行簡單的總結。

主要優勢

(1) 當建立新的對象實例較爲複雜時,使用原型模式能夠簡化對象的建立過程,經過複製一個已有實例能夠提升新實例的建立效率。

(2) 擴展性較好,因爲在原型模式中提供了抽象原型類,在客戶端能夠針對抽象原型類進行編程,而將具體原型類寫在配置文件中,增長或減小產品類對原有系統都沒有任何影響。

(3) 原型模式提供了簡化的建立結構,工廠方法模式經常須要有一個與產品類等級結構相同的工廠等級結構,而原型模式就不須要這樣,原型模式中產品的複製是經過封裝在原型類中的克隆方法實現的,無須專門的工廠類來建立產品。

(4) 可使用深克隆的方式保存對象的狀態,使用原型模式將對象複製一份並將其狀態保存起來,以便在須要的時候使用(如恢復到某一歷史狀態),可輔助實現撤銷操做。

主要缺點

(1) 須要爲每個類配備一個克隆方法,並且該克隆方法位於一個類的內部,當對已有的類進行改造時,須要修改源代碼,違背了「開閉原則」。

(2) 在實現深克隆時須要編寫較爲複雜的代碼,並且當對象之間存在多重的嵌套引用時,爲了實現深克隆,每一層對象對應的類都必須支持深克隆,實現起來可能會比較麻煩。

適用場景

(1) 建立新對象成本較大(如初始化須要佔用較長的時間,佔用太多的CPU資源或網絡資源),新的對象能夠經過原型模式對已有對象進行復制來得到,若是是類似對象,則能夠對其成員變量稍做修改。

(2) 若是系統要保存對象的狀態,而對象的狀態變化很小,或者對象自己佔用內存較少時,可使用原型模式配合備忘錄模式來實現。

(3) 須要避免使用分層次的工廠類來建立分層次的對象,而且類的實例對象只有一個或不多的幾個組合狀態,經過複製原型對象獲得新實例可能比使用構造函數建立一個新實例更加方便。

練習

設計並實現一個客戶類 Customer,其中包含一個名爲客戶地址的成員變量,客戶地址的類型爲 Address,用淺克隆和深克隆分別實現 Customer 對象的複製並比較這兩種克隆方式的異同。

練習代碼

結構圖以下:

原型模式-Customer

Customer 具體原型類以下:

/**
 * Author: YiFan
 * Date: 2018/12/12 21:26
 * Description: 具體原型類
 */
public class Customer implements Cloneable, Serializable {

    private String name;

    private int age;

    private Address address;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

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

    /**
     * 淺克隆
     * @return Customer
     * @throws CloneNotSupportedException
     */
    @Override
    public Customer clone() throws CloneNotSupportedException {
        Object object;
        object = super.clone();
        return (Customer) object;
    }
    
    /**
     * 深克隆
     * @return Customer
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public Customer deepClone() throws IOException, ClassNotFoundException {
        // 將對象寫入流中
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(this);

        // 將對象從流中取出
        ByteArrayInputStream byteArrayInputStream = new
                ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);

        // 返回Customer對象
        return (Customer) objectInputStream.readObject();
    }
}

// 實現Serializable接口爲了能夠序列化
class Address implements Serializable {

    private String addressName;

    public void setAddressName(String addressName) {
        this.addressName = addressName;
    }

    public String getAddressName() {
        return addressName;
    }
}

客戶端代碼以下:

/**
 * Author: YiFan
 * Date: 2018/12/12 21:29
 * Description: 客戶端
 */
public class Client {

    public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {

        // 建立customer1對象
        Customer customer1 = new Customer();

        // 建立address對象
        Address address = new Address();

        // 設置customer1的屬性
        customer1.setName("a");
        customer1.setAge(1);
        customer1.setAddress(address);

        // 建立customer2和customer3引用變量
        Customer customer2, customer3;

        // 克隆customer1對象(淺克隆)
        customer2 = customer1.clone();

        // 淺克隆-因此address引用類型變量只是拷貝內存地址
        System.out.println("customer1中引用類型address變量是否與customer2中相同:"
                + (customer1.getAddress() == customer2.getAddress()));

        // 克隆customer1對象(深克隆)
        customer3 = customer1.deepClone();

        // 深克隆-即拷貝值類型變量,也拷貝引用類型變量
        System.out.println("customer1中引用類型address變量是否與customer3中相同:"
                + (customer1.getAddress() == customer3.getAddress()));
    }
}

直接結果:

customer1中引用類型address變量是否與customer2中相同:true
customer1中引用類型address變量是否與customer3中相同:false
相關文章
相關標籤/搜索