1. 程式人生 > 其它 >【設計模式從入門到精通】03-原型模式

【設計模式從入門到精通】03-原型模式

原型模式

筆記來源:尚矽谷Java設計模式(圖解+框架原始碼剖析)

目錄

原型模式

1、克隆羊問題

現在有一隻羊,姓名為 Tom,年齡為 1,顏色為白色,請編寫程式建立和 Tom 羊屬性完全相同的 10 只羊

傳統方法

public class Sheep {
    private String name;
    private Integer age;

    public Sheep(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

public class Client {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Sheep sheep = new Sheep("Tom", 1, "白色");
            System.out.println(sheep);
        }
    }
}

傳統方法優缺點

  • 1)優點是比較好理解,簡單易操作
  • 2)在建立新的物件時,總是需要重新獲取原始物件的屬性,如果建立的物件比較複雜時,效率較低
  • 3)總是需要重新初始化物件,而不是動態地獲得物件執行時的狀態,不夠靈活

改進的思路分析

Java 中 Object 類是所有類的根類,Object 類提供了一個 clone 方法,該方法可以將一個 Java 物件複製一份,但是需要實現 clone 的 Java 類必須要實現一個介面 Cloneable,該介面表示該類能夠複製且具有複製的能力 ==> 原型模式

2、基本介紹

  • 1)原型模式(Prototype 模式)是指:用原型例項指定建立物件種類,並通過拷貝原型建立新的物件

  • 2)原型模式是一種建立型設計模式,允許一個物件再建立另外一個可定製的物件,無需知道如何建立的細節

  • 3)工作原理是:通過將一個原型物件傳給那個要發動建立的物件,這個要發動建立的物件通過請求原型物件拷貝它們自己來實施建立,即物件.clone()

  • 4)形象的理解:孫大聖拔出猴毛,變出其它孫大聖

3、原理結構圖(UML 類圖)

原理結構圖說明

  • 1)Prototype:原型類,宣告一個克隆自己的介面
  • 2)ConcretePrototype:具體的原型類,實現一個克隆自己的操作
  • 3)Client:讓一個原型物件克隆自己,建立一個新的物件(屬性相同)

4、原型模式解決克隆羊問題

使用原型模式改進傳統方式式,讓程式具有更高的效率和擴充套件性

UML 類圖

核心程式碼

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

    public Sheep(String name, Integer 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 Integer getAge() {
        return age;
    }

    public void setAge(Integer 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 + '\'' +
                '}';
    }

    @Override
    protected Object clone() {
        Sheep sheep = null;
        try {
            sheep = (Sheep) super.clone();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sheep;
    }
}

public class Client {
    public static void main(String[] args) {
        Sheep sheep = new Sheep("Tom", 1, "白色");
        for (int i = 0; i < 10; i++) {
            Sheep sheep1 = (Sheep) sheep.clone();
            System.out.println(sheep1);
        }
    }
}

5、JDK 原始碼分析

Spring 框架中,建立ApplicationContext時,使用的getBean方法中使用到了原型模式

6、淺拷貝和深拷貝

淺拷貝基本介紹

  • 1)對於資料型別是基本資料型別的成員變數,淺拷貝會直接進行值傳遞,也就是將該屬性值複製一份給新的物件
  • 2)對於資料型別是引用資料型別的成員變數,比如說成員變數是某個陣列、某個類的物件等,那麼淺拷貝會進行引用傳遞,也就是隻是將該成員變數的引用值(記憶體地址)複製一份給新的物件。因為實際上兩個物件的該成員變數都指向同一個例項。在這種情況下,在一個物件中修改該成員變數會影響到另一個物件的該成員變數值
  • 3)前面我們克隆羊就是淺拷貝
  • 4)淺拷貝是使用預設的 clone 方法來實現:sheep=(Sheep)super.clone();

深拷貝基本介紹

  • 1)複製物件的所有基本資料型別的成員變數值
  • 2)為所有引用資料型別的成員變數申請儲存空間,並複製每個引用資料型別成員變數所引用的物件,直到該物件可達的所有物件。也就是說,物件進行深拷貝要對整個物件進行拷貝
  • 3)深拷貝實現方式 1:重寫 clone 方法來實現深拷貝
  • 4)深拷貝實現方式 2:通過物件序列化實現深拷貝

深拷貝方式 1

public class DeepClonableTarget implements Serializable, Cloneable {

    private String cloneName;
    private String cloneClass;

    public DeepClonableTarget(String cloneName, String cloneClass) {
        this.cloneName = cloneName;
        this.cloneClass = cloneClass;
    }

    public String getCloneName() {
        return cloneName;
    }

    public void setCloneName(String cloneName) {
        this.cloneName = cloneName;
    }

    public String getCloneClass() {
        return cloneClass;
    }

    public void setCloneClass(String cloneClass) {
        this.cloneClass = cloneClass;
    }

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

public class DeepPrototype implements Serializable, Cloneable {
    private String name;
    private DeepClonableTarget deepClonableTarget;

    public DeepPrototype() {
    }

    public String getName() {
        return name;
    }

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

    public DeepClonableTarget getDeepClonableTarget() {
        return deepClonableTarget;
    }

    public void setDeepClonableTarget(DeepClonableTarget deepClonableTarget) {
        this.deepClonableTarget = deepClonableTarget;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        //基本資料型別拷貝
        Object object = super.clone();
        //引用型別拷貝
        DeepPrototype deepPrototype = (DeepPrototype) object;
        deepPrototype.deepClonableTarget = (DeepClonableTarget) deepClonableTarget.clone();
        return object;
    }
}

public class DeepTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        DeepPrototype prototype = new DeepPrototype();
        prototype.setName("宋江");
        prototype.setDeepClonableTarget(new DeepClonableTarget("及時雨", "及時雨的類"));

        DeepPrototype clone1 = (DeepPrototype) prototype.clone();
        DeepPrototype clone2 = (DeepPrototype) prototype.clone();
        DeepPrototype clone3 = (DeepPrototype) prototype.clone();
        DeepPrototype clone4 = (DeepPrototype) prototype.clone();
        DeepPrototype clone5 = (DeepPrototype) prototype.clone();
        
        System.out.println(prototype.getName() + ", " + prototype.getDeepClonableTarget().hashCode()); // 宋江, 1554874502
        System.out.println(clone1.getName() + ", " + clone1.getDeepClonableTarget().hashCode()); // 宋江, 1846274136
        System.out.println(clone2.getName() + ", " + clone2.getDeepClonableTarget().hashCode()); // 宋江, 1639705018
        System.out.println(clone3.getName() + ", " + clone3.getDeepClonableTarget().hashCode()); // 宋江, 1627674070
        System.out.println(clone4.getName() + ", " + clone4.getDeepClonableTarget().hashCode()); // 宋江, 1360875712
        System.out.println(clone5.getName() + ", " + clone5.getDeepClonableTarget().hashCode()); // 宋江, 1625635731
    }
}

深拷貝方式 2

public class DeepClonableTarget implements Serializable, Cloneable {
    private String cloneName;
    private String cloneClass;

    public DeepClonableTarget(String cloneName, String cloneClass) {
        this.cloneName = cloneName;
        this.cloneClass = cloneClass;
    }

    public String getCloneName() {
        return cloneName;
    }

    public void setCloneName(String cloneName) {
        this.cloneName = cloneName;
    }

    public String getCloneClass() {
        return cloneClass;
    }

    public void setCloneClass(String cloneClass) {
        this.cloneClass = cloneClass;
    }
}

public class DeepPrototype implements Serializable, Cloneable {
    private String name;
    private DeepClonableTarget deepClonableTarget;

    public DeepPrototype() {
    }

    public String getName() {
        return name;
    }

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

    public DeepClonableTarget getDeepClonableTarget() {
        return deepClonableTarget;
    }

    public void setDeepClonableTarget(DeepClonableTarget deepClonableTarget) {
        this.deepClonableTarget = deepClonableTarget;
    }

    public DeepPrototype 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 (DeepPrototype) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                if (ois != null) {
                    ois.close();
                }
                if (bis != null) {
                    bis.close();
                }
                if (oos != null) {
                    oos.close();
                }
                if (bos != null) {
                    bos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

public class DeepTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        DeepPrototype prototype = new DeepPrototype();
        prototype.setName("宋江");
        prototype.setDeepClonableTarget(new DeepClonableTarget("及時雨", "及時雨的類"));

        DeepPrototype clone1 = prototype.deepClone();
        DeepPrototype clone2 = prototype.deepClone();
        DeepPrototype clone3 = prototype.deepClone();
        DeepPrototype clone4 = prototype.deepClone();
        DeepPrototype clone5 = prototype.deepClone();

        System.out.println(prototype.getName() + ", " + prototype.getDeepClonableTarget().hashCode()); // 宋江, 644117698
        System.out.println(clone1.getName() + ", " + clone1.getDeepClonableTarget().hashCode()); // 宋江, 317574433
        System.out.println(clone2.getName() + ", " + clone2.getDeepClonableTarget().hashCode()); // 宋江, 885284298
        System.out.println(clone3.getName() + ", " + clone3.getDeepClonableTarget().hashCode()); // 宋江, 1389133897
        System.out.println(clone4.getName() + ", " + clone4.getDeepClonableTarget().hashCode()); // 宋江, 1534030866
        System.out.println(clone5.getName() + ", " + clone5.getDeepClonableTarget().hashCode()); // 宋江, 664223387
    }
}

方式 1 和方式 2 對比

  • 在物件引用型別的成員屬性較少時,方式 1 簡單;在物件引用型別的成員屬性較多時,方式 2 簡單
  • 在物件引用型別的成員屬性經常發生變化時,方式 1 需要同步修改,方式 2 不用修改
  • 推薦使用方式 2:耦合性低、可維護性強、擴充套件性高

7、注意事項和細節

  • 1)優點:建立新的物件比較複雜時,可以利用原型模式簡化物件的建立過程,同時也能夠提高效率
  • 2)優點:不用重新初始化物件,而是動態地獲得物件執行時的狀態
  • 3)優點如果原始物件發生變化(增加或者減少屬性),其它克隆物件的也會發生相應的變化,無需修改程式碼
  • 4)缺點:在實現深克隆的時候可能需要比較複雜的程式碼
  • 5)缺點:需要為每一個類配備一個克隆方法,這對全新的類來說不是很難,但對已有的類進行改造時,需要修改其原始碼,違背了OCP 原則,這點請同學們注意