設計模式(五)原型模式 Prototype

  • 原型模式: 

  原型模式,是指基於一個已經給定的對象,經過拷貝的方式,建立一個新的對象,這個給定對象,就是「原型」。html

  在 Java 中,原型模式體現爲 Object 的 clone() 方法。數組

  全部類均可以經過實現 Cloneable 接口,以及重寫 clone() 方法,來實現原型模式。安全

  

  • 代碼:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Liability implements Cloneable {private String code;
    private String name;
    private String category;
    private boolean isMajor;

    @Override
    protected Liability clone() throws CloneNotSupportedException {
        return (Liability) super.clone();
    }
}
@Data
@Builder
public class PolicyShallowClone implements Cloneable {

    private String code;
    private int applicantAge;
    private Liability liability;
    private List<String> specialDescriptions;

    @Override
    public PolicyShallowClone clone() throws CloneNotSupportedException {
        return (PolicyShallowClone) super.clone();
    }
}

 

  • 縮減 clone() 方法的返回類型:

  自 JDK1.5 開始,Java 引進了一個新的特性:協變返回類型(covariant return type)。架構

  即:覆蓋方法的返回類型,能夠是被覆蓋方法的返回類型的子類。app

  因此須要在 clone() 方法內部進行強轉。ide

  這體現了一條通則:永遠不要讓客戶去作任何類庫可以替客戶完成的事情。post

 

  • clone() 是一種淺度複製(Shallow Copy):
    @Test
    void testPolicy1() throws Exception {
        // Build original policy
        Liability liability = new Liability.LiabilityBuilder().code("0001").name("Liability").category("XPXA").build();
        String specialDescription1 = "text1";
        String specialDescription2 = "text2";
        List<String> specialDescriptions = new ArrayList<>(Arrays.asList(specialDescription1, specialDescription2));
        PolicyShallowClone policyA = PolicyShallowClone.builder().specialDescriptions(specialDescriptions).liability(liability).code("code001").applicantAge(18).build();
        // Call clone
        PolicyShallowClone policyB = policyA.clone();
        Assertions.assertSame(policyA.getCode(), policyB.getCode());
        Assertions.assertEquals(policyA.getCode(), policyB.getCode());
        // Assert shallow clone
        policyA.getSpecialDescriptions().add("text3");
        Assertions.assertSame(policyA.getLiability(), policyB.getLiability());
        Assertions.assertTrue(policyA.getSpecialDescriptions().size() == policyB.getSpecialDescriptions().size());
    }

 

  • 編寫一個優秀的 clone() 方法:

  克隆對象的數據來源,必須來自於 clone() 方法,因此永遠在方法內部調用 super.clone() 方法。測試

  全部的父類必須很好地實現了 clone() 方法。ui

  若是當前類包含的域引用了可變對象,須要遞歸地調用 clone() 方法。this

  若是在線程安全的類中實現 Cloneable 接口,clone() 方法必須獲得很好的同步。

 

  • 一個深度複製的 clone() 方法:
@Data
@Builder
public class PolicyDeepClone implements Cloneable {

    private String code;
    private int applicantAge;
    private Liability liability;
    private List<String> specialDescriptions;

    @Override
    public PolicyDeepClone clone() throws CloneNotSupportedException {
        PolicyDeepClone clone = (PolicyDeepClone) super.clone();
        clone.specialDescriptions = new ArrayList<>(this.specialDescriptions);
        clone.liability = this.liability.clone();
        return clone;
    }
}

 

  • 深度複製的測試:
    @Test
    void testPolicy2() throws Exception {
        // Build original policy
        Liability liability = new Liability.LiabilityBuilder().code("0001").name("Liability").category("XPXA").build();
        String specialDescription1 = "text1";
        String specialDescription2 = "text2";
        List<String> specialDescriptions = new ArrayList<>(Arrays.asList(specialDescription1, specialDescription2));
        PolicyDeepClone policyA = PolicyDeepClone.builder().specialDescriptions(specialDescriptions).liability(liability).code("code001").applicantAge(18).build();
        // Call clone
        PolicyDeepClone policyB = policyA.clone();
        // Assert deep clone
        policyA.getSpecialDescriptions().add("text3");
        Assertions.assertNotSame(policyA.getLiability(), policyB.getLiability());
        Assertions.assertFalse(policyA.getSpecialDescriptions().size() == policyB.getSpecialDescriptions().size());
    }

 

  • 有必要這麼複雜嗎?

  從上述的介紹,咱們不難發現,要完成一個優秀的 clone() 方法,存在諸多限制。

  而且,當咱們實現了 clone() 方法,在編譯器中,還會看到一條 Blocker 級別的 Sonar 警告:

  Remove this "clone" implementation; use a copy constructor or copy factory instead.

  它推薦的是一個拷貝構造器和拷貝工廠。

 

  • 拷貝構造器(Copy constructor)
@Data
@Builder
public final class PolicyCopyConstructor {

    private String code;
    private int applicantAge;
    private Liability liability;
    private List<String> specialDescriptions;

    public PolicyCopyConstructor(PolicyCopyConstructor policy) {
        this.code = policy.code;
        this.applicantAge = policy.applicantAge;
        this.liability = policy.liability;
        this.specialDescriptions = policy.specialDescriptions;
    }
}

 

  顯然,這是一個淺度複製的實現,若是須要深度複製,須要深一步挖掘,這裏不詳述。

 

  • 拷貝工廠(Copy factory):
@Data
public final class PolicyCopyFactory {

    private String code;
    private int applicantAge;
    private Liability liability;
    private List<String> specialDescriptions;

    public static PolicyCopyFactory newInstance(PolicyCopyFactory policy) {
        PolicyCopyFactory copyPolicy = new PolicyCopyFactory();
        copyPolicy.setCode(policy.getCode());
        copyPolicy.setApplicantAge(policy.getApplicantAge());
        copyPolicy.setLiability(policy.getLiability());
        copyPolicy.setSpecialDescriptions(policy.getSpecialDescriptions());
        return copyPolicy;
    }
}

 

  拷貝工廠本質上使咱們以前提到過的靜態工廠的一種變形。

  在這裏,這也是淺度複製的實現。

 

  • Copy constructor & Copy factory 的優點:
  1. 不依賴於某一種帶有風險的,語言以外的對象建立機制(clone 是 native 方法)。
  2. 不會與 final 域的正常使用發生衝突(clone 架構與引用可變對象的 final 域的正常使用是不兼容的)。
  3. 不會拋出受檢異常。
  4. 不須要類型轉換。

 

  • 《Effective Java》 第11條:謹慎地覆蓋 clone

  鑑於 clone() 方法存在這麼多限制,《Effective Java》明確指出:

  除了拷貝數組,其餘任何狀況都不該該去覆蓋 clone() 方法,也不應去調用它。

 

  • 關於深複製:

  這篇文章 第004彈:幾種通用的深度複製的方法 介紹了幾種深複製的通用方法。

相關文章
相關標籤/搜索