原型模式介紹
原型模式是一種創建型的設計模式,原型模式是指複製現有的實例來創建新的實例,不需要知道實例的創建細節。(在Java中意味使用clone方法或者序列化和反序列化)。
當創建給定類的實例的過程很昂貴或很複雜時,就需要使用原型模式。
原型模式的原理UML類圖,如下:
原理結構圖說明:
- ProtoType:原型類,實現Cloneable接口,聲明克隆自己的
抽象方法。 - ConcreteProtoType:具體的原型類,實現克隆自己的方法。
- Client:其中使用ProtoType,讓一個原型對象克隆自己,從而創建一個新的對象。
clone()方法說明:
Object中clone()方法是protected的,是淺拷貝,要使用clone()方法,要重寫它,只有實現了Cloneable接口纔可以調用該方法,否則會拋出CloneNotSupportedException異常。
clone()方法在實現上是在內存上直接複製二進制流,然後重新分配內存空間,比new出來的效率要高,clone的過程不會調用構造函數,也就是說通過這一方法創造出來的對象不會由構造函數的處理。
clone()方法創建並返回此對象的一個副本。對於任何對象x,滿足以下表達式:
- x.clone() != x爲true
- x.clone().getClass() == x.getClass()爲true
- x.clone().equals(x)一般情況下爲true,但這並不是必須要滿足的要求
淺拷貝介紹:
- 對於數據類型爲基本數據類型的成員變量,淺拷貝會直接進行值傳遞,也就是將該屬性值複製一份給新的對象。無論是淺拷貝還是深拷貝,拷貝出的新的對象指向新的地址空間。
- 對於數據類型爲引用數據類型的成員變量,如數組,對象。那麼淺拷貝將進行引用傳遞,也就是隻是將成員變量的引用值(內存地址)複製一份給新的對象。在這種情況下,若成員變量是數組或對象,因爲兩個對象的成員變量指向同一實例,在一個對象中修改該成員變量會影響到另一個對象的成員變量。
實現原型模式示例
場景:現在有一隻羊名爲li,大小爲45斤,顏色爲白色。朋友爲一個叫kai的羊,編程實現複製十隻一模一樣的羊。
下面創建一個羊的實體類,實現cloneable接口並重寫clone()方法:
public class Sheep implements Cloneable {
private String name;
private String color;
private int size;
public Sheep friend;
public Sheep(String name, String color, int size) {
this.name = name;
this.color = color;
this.size = size;
}
@Override
public String toString() {
return "Sheep [name=" + name + ", color=" + color + ", size=" + size + "]";
}
@Override
protected Sheep clone() {
Sheep sheep = null;
try {
sheep = (Sheep) super.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return sheep;
}
}
在Client類中完成複製Sheep對象
這裏很容易想到使用原型對象的屬性來new一個對象,但是這種方法總是需要重新獲取原始對象的屬性,如果創建的對象複雜就會效率很低,另外如果原型對象有修改,可能會需要修改代碼。
public class Client {
public static void main(String[] args) {
Sheep sheep = new Sheep("li", "white", 40);
sheep.friend = new Sheep("kai", "black", 45);
System.out.println(sheep.toString() + sheep.friend.hashCode());
Sheep copySheep = (Sheep) sheep.clone();
System.out.println("複製的羊:" + copySheep.toString() + copySheep.friend.hashCode());
}
}
運行程序查看結果:
可以看到兩個對象屬性friend的hashcode的值一致,即引用的對象是同一個,這說明上面使用的拷貝方式是淺拷貝。
深拷貝
1.基本介紹
深拷貝也就是複製對象的所有基本數據類型的成員變量,對於所有的引用類型的成員變量,深拷貝會爲其申請存儲空間,並會複製每個引用類型的成員變量所引用的對象。
也就是說對象進行深拷貝是要對整個對象進行拷貝。
深拷貝的兩種實現方式:重寫clone()方法和對象序列化。
2.深拷貝應用
1.方式1:重寫clone()方法。
新建一個類DeepCloneableTarget 作爲成員變量,該類的屬性只有String類型,雖然String類型也屬於引用類型,但因爲String的不可變性(可以使用反射來修改String的值),這裏使用Object類的clone即可。
public class DeepCloneableTarget implements Cloneable {
private String cloneName;
public DeepCloneableTarget(String cloneName) {
super();
this.cloneName = cloneName;
}
@Override
protected Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return super.clone();
}
}
類 DeepProtoType 將 DeepCloneableTarget類對象作爲成員變量。
public class DeepProtoType implements Cloneable {
private String name;
public DeepCloneableTarget deepCloneableTarget;
public DeepProtoType(String name, DeepCloneableTarget deepCloneableTarget) {
super();
this.name = name;
this.deepCloneableTarget = deepCloneableTarget;
}
@Override
protected DeepProtoType clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
DeepProtoType deep = (DeepProtoType) super.clone();
// 對引用類型做單獨處理,單獨進行克隆
deep.deepCloneableTarget = (DeepCloneableTarget) deepCloneableTarget.clone();
return deep;
}
}
Client類測試是否完成成員變量DeepCloneableTarget類對象的拷貝。
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
DeepCloneableTarget deepCloneableTarget = new DeepCloneableTarget("deppTarget");
DeepProtoType deepProtoType = new DeepProtoType("protoType", deepCloneableTarget);
DeepProtoType clone = deepProtoType.clone();
System.out.println(deepProtoType.deepCloneableTarget.hashCode());
System.out.println(clone.deepCloneableTarget.hashCode());
}
}
通過運行結果中的hashcode的值的不同判斷兩個作爲成員變量的對象是不同的即完成了深拷貝。
2.方式2:通過對象的序列化實現
類DeepCloneableTarget改爲實現Serializable接口
public class DeepCloneableTarget implements Serializable {
private static final long serialVersionUID = 1L;
private String cloneName;
public DeepCloneableTarget(String cloneName) {
super();
this.cloneName = cloneName;
}
}
類DeepProtoType也實現Serializable 接口,定義deepClone()方法使用序列化和反序列化實現深拷貝。
public class DeepProtoType implements Serializable {
private static final long serialVersionUID = 1L;
public String name;
public DeepCloneableTarget deepCloneableTarget;
public DeepProtoType(String name, DeepCloneableTarget deepCloneableTarget) {
super();
this.name = name;
this.deepCloneableTarget = deepCloneableTarget;
}
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);
DeepProtoType deepProtoType = (DeepProtoType) ois.readObject();
return deepProtoType;
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
return null;
} finally {
// 關閉流
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e2) {
// TODO: handle exception
e2.printStackTrace();
}
}
}
}
測試是否能完成深拷貝。
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
DeepCloneableTarget deepCloneableTarget = new DeepCloneableTarget("deppTarget");
DeepProtoType deepProtoType = new DeepProtoType("protoType", deepCloneableTarget);
DeepProtoType clone = deepProtoType.deepClone();
System.out.println(deepProtoType.name + " " + deepProtoType.deepCloneableTarget.hashCode());
System.out.println(deepProtoType.name + " " + clone.deepCloneableTarget.hashCode());
}
}
運行結果如下:
兩個對象的成員變量中的DeepCloneableTarget 對象的hashcode不同,所以兩個變量是不同的兩個對象,這同樣實現了深拷貝。
原型模式在Spring中的應用
在Spring 中我們可以定義Bean的作用域爲prototype,下面使用Java配置類的方式來註冊一個Bean。
@Configuration
public class SpringConfig {
@Bean(value = "person")
@Scope(value = "prototype")
// 或@Scope("ConfigurableBeanFactory.SCOPE_PROTOTYPE")
public Person getPerson() {
return new Person();
}
}
當使用getBean()方法獲取容器中的這個Bean時,下面通過Debug來走一遍該Bean的實例化過程:
首先調用AbstractApplicationContext的getBean()方法
這裏的getBeanFactory()是通過子類GenericApplicationContext 中的getBeanFactory()獲取DefaultListableBeanFactory。
然後getBean()方法調用AbstractBeanFactory的getBean()
接着調用本類中的doGetBean()方法,最終在這裏判斷如果是prototype,就調用這個類的createBean()方法。
總結
下面總結一下原型模式:
- 適用場景:
- 當創建新的對象複雜或需要一個類的許多對象時,就可以利用原型模式簡化對象的創建過程來提高效率。
- 類初始化需要消耗資源較多,就可以使用原型模式。
- 優點:
- 向客戶隱藏創建新實例的複雜性
- 提供讓客戶能夠產生未知類型對象的選項。即客戶在不知道要實例化何種特定類的情況下,可以製造出新的實例。
- 不用重新初始化對象,而是動態地獲得對象運行時的狀態。
- 缺點:實現深克隆時可能需要複雜的代碼。