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

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

執行結果:

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