java設計模式——原型模式(克隆羊以及淺拷貝,深拷貝)

引入需求背景

有一隻羊,名爲: tom, 年齡爲:1,顏色爲:白色,請編寫程序創建和 tom 羊 屬性完全相同的 10只羊。

最簡單的寫法

public class Test {
    public static void main(String[] args) {
        Sheep sheep = new Sheep("tom",1,"白色");


        Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
        Sheep sheep3 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
        Sheep sheep4 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());

        System.out.println(sheep);
        System.out.println(sheep2);
        System.out.println(sheep3);
        System.out.println(sheep4);

    }

}

上述寫法太死板了,不夠靈活,我們可以做一些改進。

改進思路:Java 中 Object 類是所有類的根類,Object 類提供了一個 clone()方法,該方法可以將一個 Java 對象複製 一份,但是需要實現clone的Java類必須要實現一個接口Cloneable,該接口表示該類能夠複製且具有複製的能力 => 原型模式

原型模式

  1. 原型模式(Prototype模式)是指:用原型實例指定創建對象的種類,並且通過拷貝這些原型,創建新的對象
  2. 原型模式是一種創建型設計模式,允許一個對象再創建另外一個可定製的對象,無需知道如何創建的細節
  3. 工作原理是:通過將一個原型對象傳給那個要發動創建的對象,這個要發動創建的對象通過請求原型對象拷貝它們自己來實施創建,即 對象.clone()
  4. 形象的理解:孫大聖拔出猴毛,變出其它孫大聖

所以,原型模式的核心就是一個clone()方法的操作。

通過原型模式改寫克隆羊代碼

步驟:

  1. 克隆羊(克隆的對象)實現Cloneable接口。
  2. 重寫clone()方法。
  3. 客戶端調用clone()方法,獲得一個克隆對象。

Sheep.java

/**
 * 克隆羊
 * 實現Cloneable接口
 */

public class Sheep implements Cloneable {
    private String name;
    private int age;
    private String color;

    public Sheep(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }

    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 String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\'' +
                ", hashCode='" + this.hashCode() + '\'' +
                '}';
    }


    //重寫clone()
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone(); //通過super.clone()返回該對象的克隆實例,以Object的形式
    }
}

Test.java

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Sheep sheep = new Sheep("tom",1,"白色");


        Sheep sheep2 = (Sheep) sheep.clone();  //通過調用clone方法獲取克隆對象
        Sheep sheep3 = (Sheep) sheep.clone();
        Sheep sheep4 = (Sheep) sheep.clone();

        System.out.println(sheep);
        System.out.println(sheep2);
        System.out.println(sheep3);
        System.out.println(sheep4);

//        輸出結果:
//        Sheep{name='tom', age=1, color='白色', hashCode='1625635731'}
//        Sheep{name='tom', age=1, color='白色', hashCode='1580066828'}
//        Sheep{name='tom', age=1, color='白色', hashCode='491044090'}
//        Sheep{name='tom', age=1, color='白色', hashCode='644117698'}
    }


}

深拷貝和淺拷貝

接下來,我們來思考一個問題。
剛剛我們克隆對象的時候,對象的屬性都是基本數據類型,那麼如果是一個引用類型的話?會是一個什麼樣的效果??

我們做一個簡單的示範,步驟:

  1. 在之前的sheep羊里加一個屬性,private Sheep friend;
  2. 在客戶端對這個屬性進行賦值,同時clone一個新的羊出來。
  3. 同時打印原來的羊的friend和新克隆羊的friend 的 hashcode,看結果。
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Sheep friend = new Sheep("Bob", 1, "灰色"); //朋友羊,作爲引用
        
        Sheep sheep = new Sheep("tom", 1, "白色", friend);
        Sheep sheep2 = (Sheep) sheep.clone();  //通過調用clone方法獲取克隆對象


        System.out.println(sheep.getFriend().hashCode());  // 1625635731
        System.out.println(sheep2.getFriend().hashCode()); // 1625635731

    }

}

根據結果,我們可以看到兩隻羊的朋友屬性引用的是同一只羊。
clone()和淺拷貝

淺拷貝介紹

  1. 對於數據類型是基本數據類型的成員變量,淺拷貝會直接進行值傳遞,也就是將該屬性值複製一份給新的對象。
  2. 對於數據類型是引用數據類型的成員變量,比如說成員變量是某個數組、某個類的對象等,那麼淺拷貝會進行 引用傳遞,也就是隻是將該成員變量的引用值(內存地址)複製一份給新的對象。因爲實際上兩個對象的該成 員變量都指向同一個實例。在這種情況下,在一個對象中修改該成員變量會影響到另一個對象的該成員變量值
  3. 前面我們克隆羊就是淺拷貝
  4. 淺拷貝是使用默認的 clone()方法來實現

深拷貝介紹

  1. 複製對象的所有基本數據類型的成員變量值

  2. 爲所有引用數據類型的成員變量申請存儲空間,並複製每個引用數據類型成員變量所引用的對象,直到該對象可達的所有對象。也就是說,對象進行深拷貝要對整個對象(包括對象的引用類型)進行拷貝

  3. 深拷貝實現方式1:重寫clone方法來實現深拷貝

  4. 深拷貝實現方式2:通過對象序列化實現深拷貝(推薦)

通過clone() 方法實現深拷貝

需求,sheep羊有一個家,克隆羊不要引用sheep的家,要一個屬於自己的家。
步驟:

  1. 創建一個huose類,實現Cloneable接口。
  2. 重寫sheep的clone方法。注意,clone只能克隆基本類型,不能克隆引用類型,所以,我們要把house對象做一個單獨的克隆,並進行賦值。(克隆套克隆)

huose類

public class House implements Cloneable {
    private String name;

    public House(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

Sheep類

public class Sheep implements Cloneable {
    private String name;
    private int age;
    private String color;

    private House house;


    public Sheep(String name, int age, String color, House house) {
        this.name = name;
        this.age = age;
        this.color = color;
        this.house = house;
    }

    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 String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }


    public House getHouse() {
        return house;
    }

    public void setHouse(House house) {
        this.house = house;
    }

    //重寫clone()
    @Override
    protected Object clone() throws CloneNotSupportedException {
        //這裏的克隆羊的house引用的是原先的地址
        Sheep sheep = (Sheep) super.clone();
        //把house單獨拿出來克隆一份
        House house = (House) sheep.getHouse().clone();
        //再對其賦值新house的地址
        sheep.setHouse(house);
        return sheep;
    }
}

上面的重寫clone方法對應的圖示。
先clone()一個sheep,但是此時的sheep的引用地址是原先的。
所以我們要繼續開闢一個house的克隆,再將引用替換。
重寫clonef方法圖示

測試類

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        House house = new House("新疆"); //房子在新疆

        Sheep sheep = new Sheep("tom", 1, "白色", house);
        Sheep sheep2 = (Sheep) sheep.clone();  //通過調用clone方法獲取克隆對象


        System.out.println(sheep.getHouse().hashCode());  // 1625635731
        System.out.println(sheep2.getHouse().hashCode()); // 1580066828
        //兩個對象的hashcode不一樣,所以引用的是兩個對象


    }

}
通過對象序列化實現深拷貝

原理:
就是將對象通過序列化,以流的方式輸出(這時會將引用類型拷貝一個新的地址出來),再用輸入流去接收。

步驟:
1、sheep類以及house類要實現Serializable接口,才能做一個序列化的操作。
2、在sheep類裏編寫deepClone()方法。
3、客戶端調用deepClone()方法返回新的深拷貝對象。


public Sheep deepClone() {
        //創建流對象
         ByteArrayOutputStream bos = null;
         ObjectOutputStream oos = null;
         ByteArrayInputStream bis = null;
         ObjectInputStream ois = null;

        try {
        	//序列化
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);  //當前這個對象以對象流的方式輸出
			
			//反序列化
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            return (Sheep) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            //關閉流
            try {
                bos.close();
                oos.close();
                bis.close();
                ois.close();
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }

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