GOF(五)-原型模式【推薦】

原型模式(Prototype Pattern)

原型模式主要用於創建重複的對象,同時又能保證性能,所以是創建型


在使用原型模式時,我們需要首先創建一個原型對象,再通過複製這個原型對象,來創建更多同類型的對象。

使用原型複製對象,性能很高,所以常用於創建大對象,或者初始化繁瑣的對象:比如遊戲裏面的地圖等等。

適用場景:

  • 一是類初始化需要消化非常多的資源,這個資源包括數據、硬件資源等;

  • 二是通過 new 產生一個對象需要非常繁瑣的數據準備或訪問權限,則可以使用原型模式;

  • 三是一個對象需要提供給其他對象訪問,而且各個調用者可能都需要修改其值時,可以考慮使用原型模式拷貝多個對象供調用者使用。

注意:原型模式很少單獨出現,一般是和工廠方法模式一起出現,通過 clone的方法創建一個對象,然後由工廠方法提供給調用者。

UML的相關知識,可以訪問我的另外一篇博文
在這裏插入圖片描述
網上的接口繼承的方式實現起來較爲簡單:
通過ClassA implements Cloneable ,然後加入clone()方法即可【但是這是 淺拷貝

    public Object clone() {
        Object clone = null;
        try {
            clone = super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return clone;
    }

測試代碼:

// ClassA 實現了 Cloneable 接口
ClassA a = new ClassA();
a.setXXX();

ClassA b = (ClassA) c.clone();
b.setXXX();

以上方法不過多贅述


這篇文章主要通過調用對象,實現淺拷貝和深拷貝。
在這裏插入圖片描述

看完圖說理論:

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

在這裏插入圖片描述

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


說完了淺拷貝深拷貝,正式開始編碼,還是拿遊戲地圖舉例子

非常重要:大家着重看clone()方法

首先實現淺拷貝(Shallow Copy):

總地圖GameMap代碼:

public class ShallowCopyGameMap {

    private String tree;

    private String mountain;

    public String getTree() {
        return tree;
    }

    public void setTree(String tree) {
        this.tree = tree;
    }

    public String getMountain() {
        return mountain;
    }

    public void setMountain(String mountain) {
        this.mountain = mountain;
    }

    public Object clone() {
        Object o = null;
        try {
            o = super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e.toString());
        }
        return o;
    }

    @Override
    public String toString() {
        return "{" +
                "tree='" + tree + '\'' +
                ", mountain='" + mountain + '\'' +
                '}';
    }
}

LOL遊戲地圖LOLGameMap如下:

/**
 * @description:
 * 淺拷貝
 */
public class ShallowCopyLOLGameMap implements Cloneable {

    private String dragon;

    private ShallowCopyGameMap gameMap; // 關聯GameMap對象


    public ShallowCopyLOLGameMap(String dragon, 
    						ShallowCopyGameMap gameMap) {
        this.dragon = dragon;
        this.gameMap = gameMap;
    }

    public String getDragon() {
        return dragon;
    }

    public void setDragon(String dragon) {
        this.dragon = dragon;
    }

    public ShallowCopyGameMap getGameMap() {
        return gameMap;
    }

    public void setGameMap(String tree, String mountain) {
        gameMap.setTree(tree);
        gameMap.setMountain(mountain);
    }

    public Object clone() {
        Object clone = null;
        try {
            clone = super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return clone;
    }

    @Override
    public String toString() {
        return "{" +
                "dragon='" + dragon + '\'' +
                ", gameMap=" + gameMap +
                '}';
    }
}

這兩個類基本相似,都實現了Cloneable接口

接下來進行測試:

public static void main(String[] args) {
        System.out.println("淺拷貝測試");
        ShallowCopyGameMap gameMap = new ShallowCopyGameMap();
        ShallowCopyLOLGameMap lolGameMap1 = 
        			new ShallowCopyLOLGameMap("納什男爵", gameMap);
        lolGameMap1.setGameMap("樹","山");

        System.out.println("地圖1:" + lolGameMap1);

        ShallowCopyLOLGameMap lolGameMap2 = 
        				(ShallowCopyLOLGameMap) lolGameMap1.clone();
        lolGameMap2.setGameMap("樹2", "山2");
        System.out.println("地圖2:" + lolGameMap2);


        ShallowCopyLOLGameMap lolGameMap3 = 
        				(ShallowCopyLOLGameMap) lolGameMap2.clone();
        lolGameMap3.setGameMap("樹3", "山3");
        System.out.println("地圖3:" + lolGameMap3);

        System.out.println("重新查看前兩個地圖: ");
        System.out.println("地圖1:" + lolGameMap1);
        System.out.println("地圖2:" + lolGameMap2);
    }

測試結果:

淺拷貝測試
地圖1{dragon='納什男爵', gameMap={tree='樹', mountain='山'}}
地圖2{dragon='納什男爵', gameMap={tree='樹2', mountain='山2'}}
地圖3{dragon='納什男爵', gameMap={tree='樹3', mountain='山3'}}
重新查看前兩個地圖: 
地圖1{dragon='納什男爵', gameMap={tree='樹3', mountain='山3'}}
地圖2{dragon='納什男爵', gameMap={tree='樹3', mountain='山3'}}

這就是淺拷貝的測試結果,實際開發中要非常留意這個問題,最好在測試時將之前的對象再進行測試輸出一次


我這裏提供兩種方式實現深拷貝:串行化非串行化

非串行化方式實現:(還是通過實現Cloneable接口,注意這裏的clone()方法與淺拷貝的區別)

深拷貝(非串行化):總地圖GameMap代碼:代碼與淺拷貝相同,不過多解釋

public class DeepCopyGameMap2 implements Cloneable {

    private String tree;

    private String mountain;

    public String getTree() {
        return tree;
    }

    public void setTree(String tree) {
        this.tree = tree;
    }

    public String getMountain() {
        return mountain;
    }

    public void setMountain(String mountain) {
        this.mountain = mountain;
    }

    public Object clone() {
        Object o = null;
        try {
            o = super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e.toString());
        }
        return o;
    }

    @Override
    public String toString() {
        return "{" +
                "tree='" + tree + '\'' +
                ", mountain='" + mountain + '\'' +
                '}';
    }
}

深拷貝(非串行化):LOL遊戲地圖LOLGameMap如下:

特別注意:注意這裏的clone()方法與淺拷貝的區別:

/**
 * @description:
 * 通過轉換爲二進制進行深拷貝
 */
public class DeepCopyLOLGameMap2 implements Cloneable {

    private String dragon;

    private DeepCopyGameMap2 deepCopyGameMap;


    public DeepCopyLOLGameMap2(String dragon, 
    						DeepCopyGameMap2 deepCopyGameMap) {
        this.dragon = dragon;
        this.deepCopyGameMap = deepCopyGameMap;
    }

    public String getDragon() {
        return dragon;
    }

    public void setDragon(String dragon) {
        this.dragon = dragon;
    }

    public DeepCopyGameMap2 getGameMap() {
        return deepCopyGameMap;
    }

    public void setDeepCopyGameMap2(String tree, String mountain) {
        deepCopyGameMap.setTree(tree);
        deepCopyGameMap.setMountain(mountain);
    }

    // 非串行化方式, 在LOLGameMap的clone()方法中調用總地圖的clone()方法
    public DeepCopyLOLGameMap2 clone() {
        DeepCopyLOLGameMap2 clone = null;
        try {
            clone = (DeepCopyLOLGameMap2)super.clone();

        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        assert clone != null; // 非空判斷,可以忽略
        // 深淺拷貝的區別在這裏!!!!!  
        // 使用了GameMap總地圖的clone()方法
        // GameMap的字段也是淺拷貝中出錯變化的地方
        clone.deepCopyGameMap = 
        				(DeepCopyGameMap2) deepCopyGameMap.clone();
        return clone;
    }

    @Override
    public String toString() {
        return "{" +
                "dragon='" + dragon + '\'' +
                ", deepCopyGameMap=" + deepCopyGameMap +
                '}';
    }
}

深拷貝(非串行化)測試代碼:

public static void main(String[] args) {
        System.out.println("深拷貝測試----  非串行化方式");
        DeepCopyGameMap2 deepCopyGameMap = new DeepCopyGameMap2();
        deepCopyGameMap.setTree("樹");
        deepCopyGameMap.setMountain("山");
        DeepCopyLOLGameMap2 lolGameMap1 = 
        		new DeepCopyLOLGameMap2("納什男爵",deepCopyGameMap);
        lolGameMap1.setDeepCopyGameMap2("樹","山");

        System.out.println("地圖1:" + lolGameMap1);

        DeepCopyLOLGameMap2 lolGameMap2 = 
        					(DeepCopyLOLGameMap2) lolGameMap1.clone();
        lolGameMap2.setDeepCopyGameMap2("樹2", "山2");
        System.out.println("地圖2:" + lolGameMap2);


        DeepCopyLOLGameMap2 lolGameMap3 = 
        					(DeepCopyLOLGameMap2) lolGameMap2.clone();
        lolGameMap3.setDeepCopyGameMap2("樹3", "山3");
        System.out.println("地圖3:" + lolGameMap3);

        System.out.println("重新查看前兩個地圖: ");
        System.out.println("地圖1:" + lolGameMap1);
        System.out.println("地圖2:" + lolGameMap2);
    }
}

測試結果如下:

深拷貝測試----  非串行化方式
地圖1{dragon='納什男爵', deepCopyGameMap={tree='樹', mountain='山'}}
地圖2{dragon='納什男爵', deepCopyGameMap={tree='樹2', mountain='山2'}}
地圖3{dragon='納什男爵', deepCopyGameMap={tree='樹3', mountain='山3'}}
重新查看前兩個地圖: 
地圖1{dragon='納什男爵', deepCopyGameMap={tree='樹', mountain='山'}}
地圖2{dragon='納什男爵', deepCopyGameMap={tree='樹2', mountain='山2'}}

串行化方式實現:(必須實現Serializable)

深拷貝(串行化):總地圖GameMap代碼:

public class DeepCopyGameMap implements Serializable {

    private String tree;

    private String mountain;

    public String getTree() {
        return tree;
    }

    public void setTree(String tree) {
        this.tree = tree;
    }

    public String getMountain() {
        return mountain;
    }

    public void setMountain(String mountain) {
        this.mountain = mountain;
    }

    public Object clone() {
        Object o = null;
        try {
            o = super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e.toString());
        }
        return o;
    }

    @Override
    public String toString() {
        return "{" +
                "tree='" + tree + '\'' +
                ", mountain='" + mountain + '\'' +
                '}';
    }
}

深拷貝(串行化):LOL遊戲地圖LOLGameMap如下:

/**
 * @description:
 * 通過串行化方式進行深拷貝
 */
public class DeepCopyLOLGameMap implements Serializable {

    private String dragon;

    private DeepCopyGameMap deepCopyGameMap;


    public DeepCopyLOLGameMap(String dragon, 
    					DeepCopyGameMap deepCopyGameMap) {
        this.dragon = dragon;
        this.deepCopyGameMap = deepCopyGameMap;
    }

    public String getDragon() {
        return dragon;
    }

    public void setDragon(String dragon) {
        this.dragon = dragon;
    }

    public DeepCopyGameMap getDeepCopyGameMap() {
        return deepCopyGameMap;
    }

    public void setDeepCopyGameMap(String tree, String mountain) {
        deepCopyGameMap.setTree(tree);
        deepCopyGameMap.setMountain(mountain);
    }

    // 串行化方式進行深拷貝
    public DeepCopyLOLGameMap clone() {
        DeepCopyLOLGameMap clone = 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);

            clone = (DeepCopyLOLGameMap)ois.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return clone;
    }

    @Override
    public String toString() {
        return "{" +
                "dragon='" + dragon + '\'' +
                ", deepCopyGameMap=" + deepCopyGameMap +
                '}';
    }
}

深拷貝(串行化)測試代碼:

    public static void main(String[] args) throws CloneNotSupportedException {
        System.out.println("深拷貝測試--- 串行化方式");
        DeepCopyGameMap deepCopyGameMap = new DeepCopyGameMap();
        DeepCopyLOLGameMap lolGameMap1 = 
        		new DeepCopyLOLGameMap("納什男爵", deepCopyGameMap);
        lolGameMap1.setDeepCopyGameMap("樹","山");

        System.out.println("地圖1:" + lolGameMap1);

        DeepCopyLOLGameMap lolGameMap2 = 
        					(DeepCopyLOLGameMap) lolGameMap1.clone();
        lolGameMap2.setDeepCopyGameMap("樹2", "山2");
        System.out.println("地圖2:" + lolGameMap2);


        DeepCopyLOLGameMap lolGameMap3 = 
        					(DeepCopyLOLGameMap) lolGameMap2.clone();
        lolGameMap3.setDeepCopyGameMap("樹3", "山3");
        System.out.println("地圖3:" + lolGameMap3);

        System.out.println("重新查看前兩個地圖: ");
        System.out.println("地圖1:" + lolGameMap1);
        System.out.println("地圖2:" + lolGameMap2);
    }

測試結果如下:

深拷貝測試--- 串行化方式
地圖1{dragon='納什男爵', deepCopyGameMap={tree='樹', mountain='山'}}
地圖2{dragon='納什男爵', deepCopyGameMap={tree='樹2', mountain='山2'}}
地圖3{dragon='納什男爵', deepCopyGameMap={tree='樹3', mountain='山3'}}
重新查看前兩個地圖: 
地圖1{dragon='納什男爵', deepCopyGameMap={tree='樹', mountain='山'}}
地圖2{dragon='納什男爵', deepCopyGameMap={tree='樹2', mountain='山2'}}

注意LOLGameMapclone()方法


總結一下:

原型模式通過Object的clone()方法實現,由於是內存操作,無視構造方法和訪問權限,直接獲取新的對象。但對於引用類型,需使用深拷貝,其它淺拷貝即可。


代碼已經上傳到Git:請點擊訪問

如果大家對於原型模式還有更多的使用技巧和使用心得,歡迎評論並在評論中分享自己的鏈接!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章