Java設計模式學習記錄-原型模式

前言

最近一直在面試,也沒時間寫博客了,感受已經積攢了好多知識想要記錄下來了,由於在面試中遇到的沒答出來的問題,這就是本身不足的地方,而後就要去學習這部份內容,雖說本身不足的地方學習了,可是沒有應用到具體實際的地方,過段時間也仍是會忘,因此個人辦法是用博客記錄下來。java

俗話說「好記性不如爛筆頭」,在我這裏是「好記性不如爛博客」😂。面試

今天要介紹的原型模式也是建立型模式中的一種,感受叫複製方法模式或許更接地氣一些,個人理解就是用一個對象複製出另外一對象。例如《西遊記》中孫悟空拔幾根猴毛就能變出好幾個同樣的孫猴子來。其中孫悟空就是一個原型,建立孫猴子的過程就是實現原型模式的過程。編程

原型模式

原型模式介紹

原型模式是指使用原型實例來指定建立對象的種類,而且經過拷貝這些原型建立新的對象。數組

在使用原型模式時,咱們須要首先建立一個原型對象,再經過複製這個原型對象,來建立更多的同類型的對象。編程語言

如何實現複製

原型模式中究竟是如何實現複製的呢?下面介紹兩種實現方式。ide

一、通用的方式

通用的方式是在具體的原型類的複製方法中,實例化一個與自身類型同樣的對象,傳入相同的屬性值,而後將其返回。學習

以下代碼方式:測試

public class PrototypeTest {

    //屬性變量
    private String name;

    public String getName() {
        return name;
    }

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

    /**
     * 複製方法
     * @return
     */
    protected PrototypeTest clone()  {

        PrototypeTest prototypeTest = new PrototypeTest();
        prototypeTest.setName(name);
        return prototypeTest;
    }

    /**
     * 測試
     * @param args
     */
    public static void main(String[] args) {
        PrototypeTest prototypeTest = new PrototypeTest();
        prototypeTest.setName("第三");
    //複製原型 PrototypeTest cloneObject
= prototypeTest.clone(); System.out.println(Objects.toString(prototypeTest)); System.out.println(Objects.toString(cloneObject)); } }

輸出的結果是:this

PrototypeTest(name=第三)
PrototypeTest(name=第三)

這種方式通用性很高,而且與編程語言特性無關,任何一種面向對象的語言均可以使用這種形式來實現對原型的複製。spa

二、Java中的Object的clone()方法

由於在Java中全部的Java類都繼承自java.lang.Object。而Object的類中提供一個默認的clone()方法,能夠將一個Java對象複製一份。所以在Java中能夠直接使用Object提供的clone()方法來實現對象的複製,這樣實現原型模式就比較簡單了。

須要注意的是,可以調用clone()實現拷貝的Java類,必須實現一個標識接口Cloneable,表示這個Java類支持被複制,爲何說是標識接口呢,由於這個接口裏面沒有定義任何方法,只是用了標識能夠執行某些操做。若是一個類沒有實現這個接口可是調用了clone()方法,Java編譯器將拋出一個CloneNotSupportedExecption異常。

以下代碼方式:

public class PrototypeMain implements Cloneable{

    private String name;
    private int age;

    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;
    }

    /**
     * 重寫Object的clone方法
     * @return
     */
    @Override
    protected PrototypeMain clone() {
        PrototypeMain prototypeMain = null;
        try {
             prototypeMain = (PrototypeMain)super.clone();
        }catch (CloneNotSupportedException e){
            System.err.println("Not Support Cloneable");
        }
        return prototypeMain;
    }

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

    //測試
    public static void main(String[] args) {
        PrototypeMain prototypeMain = new PrototypeMain();
        prototypeMain.setName("小花");
        prototypeMain.setAge(19);
        PrototypeMain cloneObject = prototypeMain.clone();

        System.out.println(Objects.toString(cloneObject));
    }
}

運行結果:

PrototypeMain{name='小花', age=19}

此時Object類能夠理解爲抽象原型類,而實現了Cloneable接口的類至關於具體原型類。

經過複製方法所建立的對象是全新的對象,它們在內存中擁有全新的地址,一般對複製所產生的對象進行修改時,對原型對象不會形成任何影響,每個拷貝對象都是相互獨立的。經過不一樣的方式修改,能夠獲得一系列類似但不徹底相同的對象。

原型模式的結構以下圖:

在原型模式結構圖中包含以下3個角色。

Prototype(抽象原型類):這是聲明覆制方法的接口,是全部具體原型類的公共父類,能夠是抽象類,也能夠是接口,甚至能夠是實現類。在上面介紹的實現複製的第二種方法裏面的java.lang.Object類就是擔當的這個角色。

ConcretePrototype(具體原型類):實現抽象原型類中聲明的複製方法,在複製方法中返回一個與本身同類的複製對象。在上面介紹的實現複製的第二種方法裏面的PrototypeMain類就是擔當的這個角色。

Client(客戶類):讓一個原型對象複製自身,從而建立一個新的的對象。在客戶類中只須要直接實例化或經過工廠方法等方式建立一個原型對象,再經過調用該對象的複製方法,就能夠獲得多個相同的對象了。在上面介紹的實現複製的第二種方法裏面,我將main方法寫在了具體原型類中,若是將main方法提出到一個新的的使用類中,那麼這個使用類就是客戶類。

深Copy與淺Copy

淺Copy是指被複制的對象的全部變量都含有與原來的對象相同的值,而全部的對其餘對象的引用都指向原來的對象。簡單點說就是,只複製了引用,而沒有複製真正的對象內容。

深Copy是指被複制的對象的全部變量都含有與原來對象相同的值,屬性中的對象都指向被複制過的新對象中屬性,而再也不是原型對象中的屬性。簡單點說,就是深Copy把全部的對象的引用以及對象都複製了一遍,在堆中是存在兩個相互獨立的對象,以及屬性中的對象也是相互獨立的。

咱們仍是舉例來講明吧:

以下代碼,建立一個原型類。

public class ShallowCopy implements Cloneable {
    //對象屬性
    private ArrayList<String> nameList = new ArrayList<>();

    /**
     * 複製方法
     * @return
     */
    @Override
    protected ShallowCopy clone() {

        ShallowCopy shallowCopy = null;
        try{
            shallowCopy = (ShallowCopy)super.clone();

        }catch (CloneNotSupportedException e){
            e.printStackTrace();
        }
        return shallowCopy;
    }

    /**
     * 得到屬性
     * @return
     */
    public ArrayList<String> getNameList() {
        return nameList;
    }

    /**
     * 填充屬性值
     * @param name
     */
    public void setNameList(String name) {
        this.nameList.add(name);
    }
}

在客戶類種使用,進行復制。

public class ClientTest {

    @Test
    public void test(){

        //建立一個對象
        ShallowCopy shallowCopy = new ShallowCopy();
        shallowCopy.setNameList("小紅");

        //複製一個新對象
        ShallowCopy newObject = shallowCopy.clone();
        //給新對象的屬性賦值
        newObject.setNameList("大黃");

        System.out.println(shallowCopy.getNameList());
    }

}

預想的結果應該是:小紅,實際輸出:

[小紅, 大黃]

產生這種結果的緣由是由於Object類的clone()方法致使的,clone()方法在複製對象時,只是複製本對象的引用,對其內部的數組、引用對象等都不復制,仍是指向原生對象的內部元素地址,這種複製方式就是淺Copy。在實際項目中使用這種方式的仍是比較少的。通常內部的數組和引用對象纔不複製,其餘的原始類型int、long、double等類型是會被複制的。另外String類型也是會被複制的,String類裏是沒有clone()的。

那麼如何實現深Copy呢?

將上面的複製方法的代碼改造一下:

   /**
     * 複製方法
     * @return
     */
    @Override
    protected ShallowCopy clone() {

        ShallowCopy shallowCopy = null;
        try{
            shallowCopy = (ShallowCopy)super.clone();
            shallowCopy.nameList = (ArrayList<String>) this.nameList.clone();
        }catch (CloneNotSupportedException e){
            e.printStackTrace();
        }
        return shallowCopy;
    }

其餘內容不變,獲得的輸出結果是:

[小紅]

經過上述改造,咱們實現了深Copy,這樣複製出來的新對象和原型對象之間沒有任何瓜葛了。實現了互相操做互不影響的效果,其實深Copy還有一種實現方式,那就是經過本身來寫二進制流來操做對象,而後實現對象的深Copy。

使用二進制流實現深Copy

將上面的深Copy代碼進行改造,改造後的代碼以下:

public class ShallowCopy implements Serializable{
    //對象屬性
    private ArrayList<String> nameList = new ArrayList<>();

    /**
     * 複製方法
     * @return
     */
    @Override
    protected ShallowCopy clone() {

        ShallowCopy shallowCopy = null;

        try{
            //寫入當前對象的二進制流
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(this);

            //讀出二進制流產生新的對象
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);

            shallowCopy = (ShallowCopy)ois.readObject();

        }catch (IOException|ClassNotFoundException e){
            e.printStackTrace();
        }


        return shallowCopy;
    }

    /**
     * 得到屬性
     * @return
     */
    public ArrayList<String> getNameList() {
        return nameList;
    }

    /**
     * 填充屬性值
     * @param name
     */
    public void setNameList(String name) {
        this.nameList.add(name);
    }
}

客戶使用類內容不變。運行結果以下:

[小紅]

須要注意的是經過這種方式來進行深Copy時,原型類必須實現Serializable接口,這樣才能將執行序列化將對象轉爲二進制數據。

深Copy還有另外一點須要注意的是,若是原型類中的屬性是一個引用類型的對象,這個屬性是不能用final修飾的,若是被final修飾後會編譯出錯。final修飾的屬性是不容許被從新賦值的。因此要使用深Copy時,在成員屬性上不要使用final.

相關文章
相關標籤/搜索