設計模式——原型模式
原型模式(Prototype Pattern):是用於建立重複物件,同時又能保證效能。這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式。這種模式是實現了一個原型介面,該介面用於建立當前物件的克隆。當直接建立物件的代價比較大時,則採用這種模式。
思想:Java 中 Object 類是所有類的根類,Object 類提供了一個 clone() 方法,該方法可以將一個 Java 物件複製一份,但是需要實現 clone 的 Java 類必須要實現一個介面 Cloneable,該介面表示該類能夠複製且具有複製的能力(原型模式)。
一、基本介紹
● 原型模式:用原型例項指定建立物件的種類,並且通過拷貝這些原型,建立新的物件。
● 原型模式是一種建立型設計模式,允許一個物件再建立另外一個可定製的物件,無需知道如何建立的細節。
● 工作原理是:通過將一個原型物件傳給那個要發動建立的物件,這個要發動建立的物件通過請求原型對像拷貝它們自己來實現建立,及物件的clone()。
二、類圖
原理結構圖說明 :1)、Prototype:原型類,宣告一個克隆自己的介面 clone。
2)、ConcretePrototype:具體的原型類,實現一個克隆自己的操作。
3)、Client 讓一個原型物件克隆自己,從而建立一個新的物件(相當於屬性)。
三、原型模式案例分析
【1】克隆類需要實現 Cloneable 重寫 clone 方法。
1 package com.yintong.principle.singleresponsibility; 2 //寫一個手機的克隆類 3 public class ConcretePrototype implements Cloneable{4 //名稱 5 private String name; 6 //號碼 7 private Long number; 8 //構造器 9 public ConcretePrototype(String name, Long number) { 10 super(); 11 this.name = name; 12 this.number = number; 13 } 14 15 @Override 16 public String toString() { 17 return"ConcretePrototype [name=" + name + ", number=" + number + "]"; 18 } 19 // 克隆用到的主要部分 20 @Override 21 protected Object clone() throws CloneNotSupportedException { 22 ConcretePrototype ConcretePrototype = null; 23 try { 24 ConcretePrototype = (ConcretePrototype) super.clone(); 25 }catch (Exception e) { 26 System.out.println(e.getMessage()); 27 } 28 return ConcretePrototype; 29 } 30 }
【2】客戶端呼叫 clone 方法,實現原型模式。
1 public class Client { 2 public static void main(String[] args) throws CloneNotSupportedException { 3 //建立一個物件 4 ConcretePrototype prototype = new ConcretePrototype("華為", new Long(1568889932)); 5 //通過原型模式完成物件的建立 克隆 6 ConcretePrototype p2 = (ConcretePrototype)prototype.clone(); 7 } 8 }
四、Spring 中的應用
【1】當 scope 配置為 prototype 時,表示原型模式。
1 <!-- 這裡我們的 scope="prototype" 即 原型模式來建立 --> 2 <bean id="id01" class="com.atguigu.spring.bean.Monster" scope="prototype"/>
【2】檢視底層呼叫:
1 else if (mbd.isPrototype()) { 2 // It's a prototype -> create a new instance. 3 Object prototypeInstance = null; 4 try { 5 beforePrototypeCreation(beanName); 6 prototypeInstance = createBean(beanName, mbd, args); 7 } 8 finally { 9 afterPrototypeCreation(beanName); 10 } 11 // *** 建立一個代理類 *** 12 bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); 13 }
五、淺拷貝
● 對於資料型別是基本資料型別的成員變數,淺拷貝會直接進行值傳遞,也就是將該屬性值複製一份給新物件。
● 對於資料型別是引用型別的成員變數,比如說成員變數是某個陣列、某個類的物件等,那麼淺拷貝會進行引用傳值,也就是隻是將成員變數的引用值(記憶體地址)複製一份給新物件。因為實際上兩個物件的該成員變數都指向同一個例項。在這種情況下,在一個物件中修改該成員變數會影響到另一個物件的該成員變數值。
● 淺拷貝是使用預設的 clone() 方法來實現。
六、深拷貝
● 複製物件的所有基本資料型別的成員變數值
● 為所有引用資料型別的成員變數申請儲存空間,並複製每個引用資料型別成員變數所引用的物件,直到該物件可達的所有物件。也就是說,物件進行深拷貝要對整個物件進行拷貝。
● 深拷貝的實現方式有兩種,第一種是重寫 clone 方法來實現深拷貝,第二種是通過序列化實現深拷貝,也是推薦的一種。
七、深克隆的兩種實現方法
【1】通過呼叫引用型別的克隆方法,實現深拷貝。缺點就是當引用型別多時,不建議採用。
1 public class DeepClone implements Cloneable{ 2 //基本資料型別 3 private String name; 4 //引用資料型別 5 private Spare spare; 6 7 //重寫clone方法,呼叫引用型別的克隆方法 8 @Override 9 protected Object clone() throws CloneNotSupportedException { 10 DeepClone deepClone = null; 11 deepClone = (DeepClone)super.clone(); 12 13 //克隆引用資料型別 14 deepClone.spare = (Spare) spare.clone(); 15 return deepClone; 16 } 17 }
【2】通過序列化的方式,實現深拷貝:也是建議使用的方法。
1 public class DeepClone implements Serializable{ 2 /** 3 * 序列化 ID 4 */ 5 private static final long serialVersionUID = 1L; 6 //資料型別 略。。。。 7 //重寫clone方法,呼叫引用型別的克隆方法 8 protected Object deepClone(){ 9 ByteArrayOutputStream BOStream = null; 10 ObjectOutputStream OOSream = null; 11 ByteArrayInputStream BIStream = null; 12 ObjectInputStream OIStream =null; 13 try { 14 //序列化 15 BOStream = new ByteArrayOutputStream(); 16 OOSream = new ObjectOutputStream(BOStream); 17 //將當前物件寫入流中 18 OOSream.writeObject(this); 19 20 //反序列化 21 BIStream = new ByteArrayInputStream(BOStream.toByteArray()); 22 OIStream = new ObjectInputStream(BIStream); 23 DeepClone deepClone = (DeepClone) OIStream.readObject(); 24 return deepClone; 25 } catch (Exception e) { 26 e.printStackTrace(); 27 return null; 28 }finally { 29 try { 30 BOStream.close(); 31 OOSream.close(); 32 BIStream.close(); 33 OIStream.close(); 34 } catch (Exception e2) { 35 e2.printStackTrace(); 36 } 37 } 38 } 39 }
八、原型模式的注意事項和細節
1)、建立新的物件比較複雜時,可以利用原型模式簡化物件的建立過程,同時也能夠提高效率。
2)、不用重新初始化物件,而是動態地獲得物件執行時的狀態。
3)、如果原始物件發生變化(增加或者減少屬性),其他克隆物件也會發生變化,無需修改程式碼。
4)、在實現深克隆的時候可能需要比較複雜的程式碼。
5)、缺點:需要為每一個配置類配置一個克隆方法,這對全新的類來說不是很難,但對已有的類進行改造時,需要修改其原始碼,違背了 ocp 原則。