23種設計模式-原型設計模式介紹加實戰

2022-11-05 18:00:29

1、描述

用原型範例指定建立物件的種類,並且通過拷貝這些原型物件的屬性來建立新的物件。通俗點的意思就是一個物件無需知道任何建立細節就可以建立出另外一個可客製化的物件。可以簡單看作為複製、貼上操作。

原型模式的克隆分為淺克隆和深克隆。

  • 淺克隆

克隆物件的屬性和原物件完全相同,基本型別的屬性屬於值傳遞,改變一個物件的值,另一個不會受影響。對於參照資料型別的屬性,仍指向原有屬性所指向的物件的記憶體地址。參照型別是傳遞參照,指向同一片記憶體空間,改變一個物件的參照型別的值,另一個物件也會隨之改變。但是這裡需要注意 String 型別卻是一個特殊,String雖然屬於參照型別,但是 String 類是不可改變的,它是一個常數,一個物件呼叫 clone 方法,克隆出一個新物件,這時候兩個物件的同一個 String 型別的屬性是指向同一片記憶體空間的,但是如果改變了其中一個,會產生一片新的記憶體空間,此時該物件的這個屬性的參照將指向這片新的記憶體空間,此時兩個物件的String型別的屬性指向的就是不同的2片記憶體空間,改變一個不會影響到另一個,可以當做基本型別來使用。

  • 深克隆

克隆物件的所有屬性都會被克隆,不再指向原有物件地址。

2、適用性

該設計模式使用場景很廣泛,日常開發中難免有操作某個物件時又不想影響原有物件的情況,具體還需要看業務需求使用淺克隆或者是深克隆。

3、實現邏輯

  • 抽象原型類:規定了具體原型物件必須實現的的 clone() 方法。
  • 具體原型類:實現抽象原型類的 clone() 方法,它是可被複制的物件。
  • 存取類:使用具體原型類中的 clone() 方法來複制新的物件。

Java 中通過 Object 類中提供的 clone() 方法和 Cloneable 介面來實現淺克隆,clone() 方法由 native 關鍵字修飾,通過本地方法拷貝地址值來實現,不但效率高還免去我們手動實現的煩惱。 Cloneable 介面充當抽象原型類,而我們實現了 Cloneable 介面的子實現類就是具體的原型類。

需要注意由於 Object 本身沒有實現 Cloneable 介面,所以不重寫 clone 方法並且進行呼叫的話會發生 CloneNotSupportedException 異常。

4、實戰程式碼

4.1 淺克隆

/**
 * 原型類參照類
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-05 15:29:57
 */
@Setter
@Getter
@ToString
public class Account {
    private String name;
}

/**
 * 具體的原型類
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-10-28 17:00:11
 */
@Getter
@Setter
@ToString
public class Member implements Cloneable {

    private int id;

    private String name;

    private Account account;

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

/**
 * 測試類
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-05 15:34:36
 */
public class ShallowClone {
    public static void main(String[] args) throws CloneNotSupportedException {
        Account account = new Account();
        account.setName("account");

        Member member = new Member();
        member.setId(1);
        member.setName("member");
        member.setAccount(account);

        Member cloneMember = member.clone();

        // clone 物件與原物件的比較
        System.out.println(cloneMember == member);
        System.out.println(cloneMember.getId() == member.getId());
        System.out.println(cloneMember.getAccount() == member.getAccount());
        System.out.println(cloneMember.getName() == member.getName());

        // 修改 clone 物件屬性
        account.setName("newAccount");
        cloneMember.setId(2);
        cloneMember.setName("newMember");

        // 檢視原型物件
        System.out.println(member);
        System.out.println(cloneMember);
    }
}

執行結果:

從結果不難看出,我們得到的 clone 物件是一個新的物件,但是屬性的值或者參照地址都是一樣的。再修改 clone 物件後,基本型別的屬性的值不會跟著修改, String 型別的屬性由於自身特性指向了新的地址值,而我們 Account 類的屬性,在 clone 物件的值被修改,兩個物件的 account 屬性都指向同一個記憶體地址值,所以會被一起修改。我在生產開發中需要注意原型模式物件參照屬性謹慎操作。

4.2 深克隆

進行深克隆需要使用物件流。不用實現 Cloneable 介面,注意實現序列化介面 Serializable

/**
 * 原型類參照類
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-05 15:29:57
 */
@Setter
@Getter
@ToString
public class Account implements Serializable {
    private String name;
}

/**
 * 具體的原型類
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-10-28 17:00:11
 */
@Getter
@Setter
@ToString
public class Member implements Serializable {

    private int id;

    private String name;

    private Account account;
}

/**
 * 深克隆測試類
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-05 16:26:57
 */
public class DeepClone {
    public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
        Account account = new Account();
        account.setName("account");

        Member member = new Member();
        member.setId(1);
        member.setName("member");
        member.setAccount(account);

        //建立物件輸出流物件
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("XXX/member.txt"));
        //將c1物件寫出到檔案中
        oos.writeObject(member);
        oos.close();

        //建立物件出入流物件
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("XXX/member.txt"));
        //讀取物件
        Member cloneMember = (Member) ois.readObject();
        //獲取c2獎狀所屬學生物件
        Account newAccount = cloneMember.getAccount();
        newAccount.setName("newAccount");

        // 檢視原型物件
        System.out.println(member);
        System.out.println(cloneMember);
    }
}

執行結果:

通過序列化和反序列化得到的物件地址值不同來達到目的