介紹:用原型執行個體指定建立對象的種類,並且通過拷貝這些原型建立新的對象。這個模式相對簡單,其實我們只需要掌握對象拷貝的知識。
情境:我們在使用對象的時候可能會通過下面的方式來建立新的對象。但是,這樣只是將stu2引用指向stu1,而並非建立一個新的對象。在我們之後對stu2的屬性改變時,也會造成stu1的改變。
Student stu2 = stu1;
一、對象拷貝的概念
我們在實現對象拷貝的時候,首先應該實現Cloneable介面,並重寫clone()方法。
這裡,我們不得不提到對象拷貝的概念—也就是淺拷貝和深拷貝。
淺拷貝:
對實值型別的成員變數進行值的複製,對參考型別的成員變數只複製引用,不複製引用的對象。也就是說,如果拷貝的對象中還有物件類型的屬性,那麼它的在之後的變換中是會對所用的引用造成影響的。
深拷貝:
對實值型別的成員變數進行值的複製,對參考型別的成員變數也進行引用對象的複製。這樣就避免了上面的這種情況的發生。 二、淺拷貝
情境:小米買了個PSP,小明看到了也讓小米幫忙買了一個。但是,小米買來的時候啟用用的是自己的帳號。小明在用了一段時間後想修改使用者名稱密碼,並且增加記憶體大小。記憶體是增加了,但是由於自己修改了帳號,導致小米的帳號不能用了。。。
帳號
public class Account { private String name; private String password; @Override public String toString() { return "Acount name:" + name + ",Acount name:" + password; } // setter and getter}
PSP
public class PSP implements Cloneable { private String color; private double price; private Account account; private int ram; @Override protected Object clone() { PSP psp = null; try { psp = (PSP) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return psp; } @Override public String toString() { return color + "," + ram + "," + price + "," + account; } // setter and getter}
測試
public static void main(String[] args) { PSP psp1 = new PSP(); Account acc = new Account(); acc.setName("小米"); acc.setPassword("123"); psp1.setAccount(acc); psp1.setColor("red"); psp1.setPrice(1600.0); psp1.setRam(8); System.out.println("小米:" + psp1.toString()); PSP psp2 = (PSP) psp1.clone(); System.out.println("小明:" + psp2.toString()); System.out.println("小明修改記憶體和帳號=========="); psp2.getAccount().setName("小明"); psp2.getAccount().setPassword("555"); psp2.setRam(16); System.out.println("小米:" + psp1.toString()); System.out.println("小明:" + psp2.toString()); }
輸出
小米:red,8,1600.0,Acount name:小米,Acount name:123小明:red,8,1600.0,Acount name:小米,Acount name:123小明修改記憶體和帳號==========小米:red,8,1600.0,Acount name:小明,Acount name:555小明:red,16,1600.0,Acount name:小明,Acount name:555
可以看到,淺拷貝的方式,基本屬性是進行複製,而物件類型則還是使用原來的引用(也可通過查看兩者Account的hashCode來說明)。 三、深拷貝
實現深拷貝,我們可以藉助序列化(Serialization),我們可以將對象序列化寫入流中,然後通過還原序列化再擷取,這樣就能得到一個完整的拷貝。這裡需要注意,我們的類需要實現Serializable介面,否則會報java.io.NotSerializableException的異常。
帳號
public class Account implements Serializable { private static final long serialVersionUID = -7747721962690262418L; private String name; private String password; @Override public String toString() { return "Acount name:" + name + ",Acount name:" + password; } // setter and getter}
PSP
public class PSP implements Cloneable, Serializable { private static final long serialVersionUID = 6401635201114419015L; private String color; private double price; private Account account; private int ram; @Override protected Object clone() { PSP psp = null; try { // 將對象寫到流裡 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos; oos = new ObjectOutputStream(bos); oos.writeObject(this); // 從流裡讀回來 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); psp = (PSP) ois.readObject(); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } return psp; } @Override public String toString() { return color + "," + ram + "," + price + "," + account; } // setter and getter}
測試
public static void main(String[] args) { PSP psp1 = new PSP(); Account acc = new Account(); acc.setName("小米"); acc.setPassword("123"); psp1.setAccount(acc); psp1.setColor("red"); psp1.setPrice(1600.0); psp1.setRam(8); System.out.println("小米:" + psp1.toString()); PSP psp2 = (PSP) psp1.clone(); System.out.println("小明:" + psp2.toString()); System.out.println("小明修改記憶體和帳號=========="); psp2.getAccount().setName("小明"); psp2.getAccount().setPassword("555"); psp2.setRam(16); System.out.println("小米:" + psp1.toString()); System.out.println("小明:" + psp2.toString()); }
輸出
小米:red,8,1600.0,Acount name:小米,Acount name:123小明:red,8,1600.0,Acount name:小米,Acount name:123小明修改記憶體和帳號==========小米:red,8,1600.0,Acount name:小米,Acount name:123小明:red,16,1600.0,Acount name:小明,Acount name:555
可以看到,這樣小明修改帳號,小明的帳號密碼都不會修改,這樣就避免上面的這種情況。
更多模式: 一天一個設計模式—分類與六大原則
更多源碼: https://github.com/oDevilo/Java-Base