java設計模式學習筆記--原型模式(淺克隆和深克隆)
1. 什麼是原型模式
原型模式屬於物件的建立模式。。原型模式允許你通過複製現有的例項來建立新的例項。
這個模式的重點在於,客戶端的程式碼在不知道要例項化何種特定類的情況下,可以製造出新的例項。在java中,一般使用clone()的方法,或者序列化。
2. 原型模式的實現
在java中,由於Object類中有一個clone()方法,所以要使用原型模式非常簡單,只要實現Cloneable的介面就可以了。
Java語言提供的Cloneable介面只起一個作用,就是在執行時期通知Java虛擬機器可以安全地在這個類上使用clone()方法。通過呼叫這個clone()方法可以得到一個物件的複製。由於Object類本身並不實現Cloneable介面,因此如果所考慮的類沒有實現Cloneable介面時,呼叫clone()方法會丟擲CloneNotSupportedException異常。
具體程式碼如下:
public class Student implements Cloneable ,Serializable{
private String name;
private transient int age;
private Book book;
//淺克隆
public Student clone(){
Student stu = null;
try {
stu = (Student) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
//深克隆
public Student deepClone() throws Exception {
Student stu = null;
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bo);
oos.writeObject(this);
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi = new ObjectInputStream(bi);
stu = (Student) oi.readObject();
return stu;
}
public Student(String name, int age, Book book) {
this.name = name;
this.age = age;
this.book = book;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Book getBook() {
return book;
}
public void setBook(Book book) {
this.book = book;
}
}
public class Book implements Serializable{
private String name;
public Book(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Client {
public static void main(String[] args){
Book book = new Book("Chinese");
Student s = new Student("Tom",12,book);
Student s1 = s.clone();
System.out.println(s==s1);
System.out.println(s.getClass()==s1.getClass());
System.out.println(s.getBook()==s1.getBook());
System.out.println("------------------------");
try {
Student s2 = s.deepClone();
System.out.println(s==s2);
System.out.println(s.getClass()==s2.getClass());
System.out.println(s.getBook()==s2.getBook());
System.out.println("-------------------");
System.out.println("s.age:"+s.getAge()+" s2.age:"+s2.getAge());
} catch (Exception e) {
e.printStackTrace();
}
}
}
執行結果如下:
在例子中可以看到,使用Clone()方法,我們可以很容易的複製一個新的例項。
但是,這樣的複製,使用的是淺克隆,那麼什麼是淺克隆哪?
3. 淺克隆與深克隆
淺克隆:
只負責克隆按值傳遞的資料(比如基本資料型別、String型別),而不復制它所引用的物件,換言之,所有的對其他物件的引用都仍然指向原來的物件。
也就是說如果被克隆的物件中,有對其他物件的引用,那麼就只複製那個物件的引用,而不是重新複製一個新的物件。
在上面的例子中,Student物件中具有對Book的引用,那麼在使用淺克隆時(使用Clone()方法),複製的s1的book和原來的book是同一個物件,所以s.getBook() ==s1.getBook() 返回 true。
深克隆:
除了淺度克隆要克隆的值外,還負責克隆引用型別的資料。那些引用其他物件的變數將指向被複制過的新物件,而不再是原有的那些被引用的物件。換言之,深度克隆把要複製的物件所引用的物件都複製了一遍,而這種對被引用到的物件的複製叫做間接複製。
深克隆的方法有,反序列化,重寫Clone()方法等。在上述例子中,就使用了序列化和反序列化的手段。由此,s.getBook() ==s1.getBook() 返回 false。
利用序列化實現深度克隆
把物件寫到流裡的過程是序列化(Serialization)過程;而把物件從流中讀出來的過程則叫反序列化(Deserialization)過程。應當指出的是,寫到流裡的是物件的一個拷貝,而原物件仍然存在於JVM裡面。
在序列化一個物件時,應該先實現Serializable介面,然後把物件(實際是物件的拷貝)寫到流裡面,然後再從流裡面反序列化出來,這樣就重新建立了一個物件。
使用這個方法有一個前提,那就是要克隆的物件所引用的所有物件都應該是可序列化的,那麼如果遇到不可序列化的物件時應該怎麼辦哪?
這時就要用到transient關鍵字了(注意:transient只能修飾變數),被transient修飾的變數在序列化時不會被序列化。在上述的例子中,Student的age變數使用了transient關鍵字修飾,所以我們可以看到s.getAge()的值為12,而s2.getAge()的值為0(int型別預設值為0)。
4.原型模式的優缺點
優點:
1、向客戶隱藏製造新例項的複雜性。
2、提供讓客戶能夠產生未知型別物件的選項。
3、在某些環境下,複製物件比建立物件更加有效。
缺點:
1、物件的複製有時相當的複雜。特別是當一個類引用不支援序列化的間接物件,或者引用含有迴圈結構的時候。
用途:
1、在一個複雜的類層次中,當系統必須從其中的許多型別建立新物件時,可以考慮原型