一、原型模式簡介
1、原型模式概述
(1)介紹
- 原型模式(Prototype Pattern)是指用原型實例指定創建對象的種類,並且通過拷貝這些原型,創建新的對象,即用於創建重複的對象。
- 原型模式屬於創建型模式,它提供了一種創建對象的最佳方式,允許一個對象再創建另外一個可定製的對象,無需知道如何創建的細節
- 這種模式是實現了一個原型接口,該接口用於創建當前對象的克隆。當直接創建對象的代價比較大時,則採用這種模式。例如,一個對象需要在一個高代價的數據庫操作之後被創建。我們可以緩存該對象,在下一個請求時返回它的克隆,在需要的時候更新數據庫,以此來減少數據庫調用。
(2)何時使用
- 當一個系統應該獨立於它的產品創建,構成和表示時。
- 當要實例化的類是在運行時刻指定時,例如,通過動態裝載。
- 爲了避免創建一個與產品類層次平行的工廠類層次時。
- 當一個類的實例只能有幾個不同狀態組合中的一種時。建立相應數目的原型並克隆它們可能比每次用合適的狀態手工實例化該類更方便一些。
(3)使用場景
- 資源優化場景。
- 類初始化需要消化非常多的資源,這個資源包括數據、硬件資源等。
- 性能和安全要求的場景。
- 通過 new 產生一個對象需要非常繁瑣的數據準備或訪問權限,則可以使用原型模式。
- 一個對象多個修改者的場景。
- 一個對象需要提供給其他對象訪問,而且各個調用者可能都需要修改其值時,可以考慮使用原型模式拷貝多個對象供調用者使用。
- 在實際項目中,原型模式很少單獨出現,一般是和工廠方法模式一起出現,通過 clone 的方法創建一個對象,然後由工廠方法提供給調用者。原型模式已經與 Java 融爲渾然一體,大家可以隨手拿來使用。
(4)注意事項
- 與通過對一個類進行實例化來構造新對象不同的是,原型模式是通過拷貝一個現有對象生成新對象的。淺拷貝實現 Cloneable,重寫,深拷貝是通過實現 Serializable 讀取二進制流。
二、原型模式示例
假如有一個人Person,姓名張三,年齡20,性別男,現在需要編寫程序創建5個完全以往的人。下面用傳統方式和原型模式進行分析
1、傳統模式
(1)傳統模式示例
創建一個Person類,其具有屬性Name,Age,Sex
public class Person {
/*屬性*/
private String Name;
private int Age;
private String Sex;
/*構造器*/
public Person(String name, int age, String sex) {
Name = name;
Age = age;
Sex = sex;
}
/*getter/setter方法,省略*/
/*重寫toString()*/
@Override
public String toString() {
return "Person{" +
"Name='" + Name + '\'' +
", Age=" + Age +
", Sex='" + Sex + '\'' +
'}';
}
}
main方法類創建Person的對象
/*傳統模式*/
public class Yxms1 {
public static void main(String[] args) {
/*創建第一個人*/
Person person1 = new Person("張三", 20, "男");
/*創建剩下九個人時,從第一個人的屬性中直接獲取屬性*/
Person person2 = new Person(person1.getName(), person1.getAge(), person1.getSex());
Person person3 = new Person(person1.getName(), person1.getAge(), person1.getSex());
Person person4 = new Person(person1.getName(), person1.getAge(), person1.getSex());
Person person5 = new Person(person1.getName(), person1.getAge(), person1.getSex());
System.out.println("person1 = " + person1);
System.out.println("person2 = " + person2);
System.out.println("person3 = " + person3);
System.out.println("person4 = " + person4);
System.out.println("person5 = " + person5);
}
}
輸出結果爲
person1 = Person{Name='張三', Age=20, Sex='男'}
person2 = Person{Name='張三', Age=20, Sex='男'}
person3 = Person{Name='張三', Age=20, Sex='男'}
person4 = Person{Name='張三', Age=20, Sex='男'}
person5 = Person{Name='張三', Age=20, Sex='男'}
(2)傳統模式分析
- 傳統模式優點是比較好理解,簡單易操作;
- 但是傳統模式在創建新的對象時,總是需要重新獲取原始對象的屬性,如果創建的對象比較複雜時,效率較低;
- 傳統模式總是需要重新初始化對象,而不是動態地獲得對象運行時的狀態,不夠靈活。
2、原型模式
(1)原型模式示例
鑑於傳統模式的缺點和不足,可以用原型模式加以改進。我們知道Java中Object類 是所有類的根類,Object類提供 了一個clone()方法,該方法可以將一個Java對象複製一份,但是需要實現clone的Java類必須要實現一個接口Cloneable,該接口表示該類能夠複製且具有複製的能力。
- 實現克隆操作,在 JAVA 繼承 Cloneable,重寫 clone(),在 .NET 中可以使用 Object 類的 MemberwiseClone() 方法來實現對象的淺拷貝或通過序列化的方式來實現深拷貝。
- 原型模式同樣用於隔離類對象的使用者和具體類型(易變類)之間的耦合關係,它同樣要求這些"易變類"擁有穩定的接口。
具體示例代碼如下
創建Person類,實現Cloneable接口,並重寫clone()方法。
/*實現Cloneable接口*/
public class Person implements Cloneable{
/*屬性*/
private String Name;
private int Age;
private String Sex;
/*構造器*/
public Person(String name, int age, String sex) {
Name = name;
Age = age;
Sex = sex;
}
/*getter/setter方法,省略*/
/*重寫toString()*/
@Override
public String toString() {
return "Person{" +
"Name='" + Name + '\'' +
", Age=" + Age +
", Sex='" + Sex + '\'' +
'}';
}
/*重寫clone()方法*/
@Override
protected Object clone(){
Person person = null;
try {
person = (Person) super.clone();
}catch (Exception e){
e.printStackTrace();
}
return person;
}
}
main()方法類創建示例
public class Yxms2 {
public static void main(String[] args) {
/*創建第一個人*/
Person person1 = new Person("張三", 20, "男");
/*使用原型模式創建(克隆)剩下的人*/
Person person2 = (Person) person1.clone();
Person person3 = (Person) person1.clone();
Person person4 = (Person) person1.clone();
Person person5 = (Person) person1.clone();
System.out.println("person1 = " + person1);
System.out.println("person2 = " + person2);
System.out.println("person3 = " + person3);
System.out.println("person4 = " + person4);
System.out.println("person5 = " + person5);
}
}
輸出結果爲
person1 = Person{Name='張三', Age=20, Sex='男'}
person2 = Person{Name='張三', Age=20, Sex='男'}
person3 = Person{Name='張三', Age=20, Sex='男'}
person4 = Person{Name='張三', Age=20, Sex='男'}
person5 = Person{Name='張三', Age=20, Sex='男'}
(2)原型模式分析
- 創建新的對象比較複雜時,可以利用原型模式簡化對象的創建過程,同時也能夠提高效率;
- 原型模式不用重新初始化對象,而是動態地獲得對象運行時的狀態;
- 如果原始對象發生變化(增加或者減少屬性),其它克隆對象的也會發生相應的編號,無需修改代碼;
- 原型模式在實現深克隆的時候可能需要比較複雜的代碼;
- 缺點是原型模式需要爲每一個類配備一個克隆方法,這對全新的類來說不是很難,但對已有的類進行改造時,需要修改其源代碼,違背了ocp原則。
三、原型模式中的淺拷貝和深拷貝
1、淺拷貝
(1)淺拷貝介紹
淺拷貝是指將對象中的所有字段複製到新的對象中。其中,值類型字段被複制到新對象中後,在新對象中的修改不會影響到原先對象的值。而新對象的引用類型則是原先對象引用類型的引用,不是引用自己對象本身。在新對象中修改引用類型的值會影響到原先對象,理論上String也是引用類型,但是由於由於該類型比較特殊,Object.MemberwiseClone()方法依舊爲其新對象開闢了新的內存空間存儲String的值,在淺拷貝中把String類型當作’值類型’即可。
- 對於數據類型是基本數據類型的成員變量,淺拷貝會直接進行值傳遞,也就是將該屬性值複製一份給新的對象;
- 對於數據類型是引用數據類型的成員變量,比如說成員變量是某個數組、某個類的對象等,那麼淺拷貝會進行引用傳遞,也就是隻是將該成員變量的引用值(內存地址)複製一份給新的對象。因爲實際上兩個對象的該成員變量都指向同一個實例。在這種情況下,在一個對象中修改該成員變量會影響到另一個對象的該成員變量值;
- 淺拷貝是使用默認的clone()方法來實現sheep = (Sheep) super.clone()。
2、深拷貝
(1)深拷貝介紹
深拷貝是指同樣也是拷貝,但是與淺拷貝不同的是,深拷貝會對引用類型重新在創新一次(包括值類型),在新對象做的任何修改都不會影響到源對象本身。
- 複製對象的所有基本數據類型的成員變量值
- 爲所有引用數據類型的成員變量申請存儲空間,並複製每個引用數據類型成員變量所引用的對象,直到該對象可達的所有對象。也就是說,對象進行深拷貝要對整個對象進行拷貝
3、淺拷貝/深拷貝示例
(1)深拷貝第一種方式
創建一個類實現Serializable和Cloneable接口,並重寫clone方法
public class DeepClone implements Serializable,Cloneable {
private static final long serialVersionUID = 1L;
private String Name;
private String Age;
/*構造器*/
public DeepClone(String Name, String Age) {
this.Name = Name;
this.Age = Age;
}
/*重寫clone方法*/
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
創建另一個類也實現Serializable和Cloneable接口,並且重寫clone方法,這個類中含有DeepClone 類的引用
public class DeepCloneType implements Serializable,Cloneable {
public String Name;
public DeepClone deepClone; //引用類型的屬性
public DeepCloneType() {
super();
}
/*重寫clone方法*/
@Override
protected Object clone() throws CloneNotSupportedException {
Object deep = null;
/*基本數據類型的克隆*/
deep = super.clone();
/*引用類型的克隆*/
DeepCloneType deepCloneType = (DeepCloneType) deep;
deepCloneType.deepClone = (DeepClone) deepClone.clone();
return deepCloneType;
}
}
main方法類調用拷貝
public class Skb {
public static void main(String[] args) throws Exception {
DeepCloneType dType = new DeepCloneType();
dType.Name = "張三";
dType.deepClone = new DeepClone("李四","");
/*深拷貝*/
DeepCloneType dType1 = (DeepCloneType) dType.clone();
System.out.println("dType.Name = " + dType.Name + " dType.deepClone = " + dType.deepClone.hashCode());
System.out.println("dType1.Name = " + dType1.Name + " dType1.deepClone = " + dType1.deepClone.hashCode());
}
}
輸出結果爲
dType.Name = 張三 dType.deepClone = 22307196
dType1.Name = 張三 dType1.deepClone = 10568834
結果顯示,引用類型的屬性的hashCode時不一樣的,說明是經過深拷貝的。
(2)深拷貝第二種方式
同樣的,創建一個類實現Serializable和Cloneable接口,並重寫clone方法
public class DeepClone implements Serializable,Cloneable {
private static final long serialVersionUID = 1L;
private String Name;
private String Target;
/*構造器*/
public DeepClone(String Name, String Target) {
this.Name = Name;
this.Target = Target;
}
/*重寫clone方法*/
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
創建另一個類也實現Serializable和Cloneable接口,這個類中含有DeepClone 類的引用,在這個類中,以序列化的方式實現深拷貝
public class DeepCloneType implements Serializable,Cloneable {
public String Name;
public DeepClone deepClone; //引用類型的屬性
public DeepCloneType() {
super();
}
/*通過對象的序列化實現深拷貝*/
public Object deepClone(){
/*創建輸入輸出流*/
ByteArrayOutputStream baos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bais = null;
ObjectInputStream ois = null;
try {
/*序列化*/
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(this);
/*反序列化*/
bais = new ByteArrayInputStream(baos.toByteArray());
ois = new ObjectInputStream(bais);
DeepCloneType deepCloneType = (DeepCloneType) ois.readObject();
return deepCloneType;
}catch (Exception e){
e.printStackTrace();
return null;
}finally {
if(baos != null){
try {
baos.close();
} catch (Exception e1) {
e1.printStackTrace();
}
}
if(oos != null){
try {
oos.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
if(bais != null){
try {
bais.close();
} catch (Exception e3) {
e3.printStackTrace();
}
}
if(ois != null){
try {
ois.close();
} catch (Exception e4) {
e4.printStackTrace();
}
}
}
}
}
main方法類調用拷貝
public class Skb {
public static void main(String[] args) throws Exception {
DeepCloneType dType = new DeepCloneType();
dType.Name = "張三";
dType.deepClone = new DeepClone("李四","");
/*深拷貝*/
DeepCloneType dType1 = (DeepCloneType) dType.deepClone();
System.out.println("dType.Name = " + dType.Name + " dType.deepClone = " + dType.deepClone.hashCode());
System.out.println("dType1.Name = " + dType1.Name + " dType1.deepClone = " + dType1.deepClone.hashCode());
}
}
輸出結果爲
dType.Name = 張三 dType.deepClone = 11110316
dType1.Name = 張三 dType1.deepClone = 17061334
結果顯示,以這種方式實現的深拷貝,引用類型的屬性的hashCode也不一樣的,說明是經過深拷貝的。