【Java設計模式】:建立型模式—原型模式
1.原型模式(Prototype Pattern)
定義:
原型(Prototype)模式是一種物件建立型模式,他採取複製原型物件的方法來建立物件的例項。使用原型模式建立的例項,具有與原型一樣的資料。
原型模式的特點:
- 由原型物件自身建立目標物件。也就是說,物件建立這一動作發自原型物件本身。
- 目標物件是原型物件的一個克隆。也就是說,通過原型模式建立的物件,不僅僅與原型物件具有相同的結構,還與原型物件具有相同的值。
- 根據物件克隆深度層次的不同,有淺度克隆與深度克隆。
2.淺複製
我們知道,一個類的定義中包括屬性和方法。屬性用於表示物件的狀態,方法用於表示物件所具有的行為。其中,屬性既可以是Java中基本資料型別,也可以是引用型別。Java中的淺複製通常使用clone()方式完成。
當進淺複製時,clone函式返回的是一個引用,指向的是新的clone出來的物件,此物件與原物件分別佔用不同的堆空間。同時,複製出來的物件具有與原物件一致的狀態。
此處物件一致的狀態是指:複製出的物件與原物件中的屬性值完全相等。
下面以複製一本書為例:
1.定義Book類和Author類:
class Author {
private String name;
private int age;
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;
}
}
class Book implements Cloneable {
private String title;
private int pageNum;
private Author author;
public Book clone () {
Book book = null;
try {
book = (Book) super.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return book;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getPageNum() {
return pageNum;
}
public void setPageNum(int pageNum) {
this.pageNum = pageNum;
}
public Author getAuthor() {
return author;
}
public void setAuthor(Author author) {
this.author = author;
}
}
2.測試:
package com.qqyumidi;
public class PrototypeTest {
public static void main(String[] args) {
Book book1 = new Book();
Author author = new Author();
author.setName("corn");
author.setAge(100);
book1.setAuthor(author);
book1.setTitle("好記性不如爛部落格");
book1.setPageNum(230);
Book book2 = book1.clone();
System.out.println(book1 == book2); // false
System.out.println(book1.getPageNum() == book2.getPageNum()); // true
System.out.println(book1.getTitle() == book2.getTitle()); // true
System.out.println(book1.getAuthor() == book2.getAuthor()); // true
}
}
由輸出的結果可以驗證說到的結論。由此我們發現:雖然複製出來的物件重新在堆上開闢了記憶體空間,但是,物件中各屬性確保持相等。對於基本資料型別很好理解,但對於引用資料型別來說,則意味著此引用型別的屬性所指向的物件本身是相同的, 並沒有重新開闢記憶體空間儲存。換句話說,引用型別的屬性所指向的物件並沒有複製。
這樣的話兩個物件共享了一個私有變數,所有人都可以改,是一個種非常不安全的方式,在實際專案中使用還是比較少的。
由此,我們將其稱之為淺複製。當複製後的物件的引用型別的屬性所指向的物件也重新得以複製,此時,稱之為深複製。
3.深複製
Java中的深複製一般是通過物件的序列化和反序列化得以實現。序列化時,需要實現Serializable介面。
下面還是以Book為例,看下深複製的一般實現過程:
1.定義Book類和Author類(注意:不僅Book類需要實現Serializable介面,Author同樣也需要實現Serializable介面!!):
class Author implements Serializable{
private String name;
private int age;
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;
}
}
class Book implements Serializable {
private String title;
private int pageNum;
private Author author;
public Book deepClone() throws IOException, ClassNotFoundException{
// 寫入當前物件的二進位制流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 讀出二進位制流產生的新物件
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Book) ois.readObject();
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getPageNum() {
return pageNum;
}
public void setPageNum(int pageNum) {
this.pageNum = pageNum;
}
public Author getAuthor() {
return author;
}
public void setAuthor(Author author) {
this.author = author;
}
}
2.測試:
public class PrototypeTest {
public static void main(String[] args) throws ClassNotFoundException, IOException {
Book book1 = new Book();
Author author = new Author();
author.setName("corn");
author.setAge(100);
book1.setAuthor(author);
book1.setTitle("好記性不如爛部落格");
book1.setPageNum(230);
Book book2 = book1.deepClone();
System.out.println(book1 == book2); // false
System.out.println(book1.getPageNum() == book2.getPageNum()); // true
System.out.println(book1.getTitle() == book2.getTitle()); // false
System.out.println(book1.getAuthor() == book2.getAuthor()); // false
}
}
從輸出結果中可以看出,深複製不僅在堆記憶體上開闢了空間以儲存複製出的物件,甚至連物件中的引用型別的屬性所指向的物件也得以複製,重新開闢了堆空間儲存。
4. 原型模式的應用場景
最後說一下,原型模式的使用場景
- 在建立物件的時候,我們不只是希望被建立的物件繼承其基類的基本結構,還希望繼承原型物件的資料。
- 希望對目標物件的修改不影響既有的原型物件(深度克隆的時候可以完全互不影響)。
- 隱藏克隆操作的細節,很多時候,對物件本身的克隆需要涉及到類本身的資料細節。
- 類初始化需要消化非常多的資源,這個資源包括資料、硬體資源等;
- 通過 new 產生一個物件需要非常繁瑣的資料準備或訪問許可權,則可以使用原型模式。
- 一個物件需要提供給其他物件訪問,而且各個呼叫者可能都需要修改其值時,可以考慮使用原型模式拷貝多個物件供呼叫者使用。在實際專案中,原型模式很少單獨出現,一般是和工廠方法模式一起出現,通過 clone的方法建立一個物件,然後由工廠方法提供給呼叫者。原型模式先產生出一個包含大量共有資訊的類,然後可以拷貝出副本,修正細節資訊,建立了一個完整的個性物件。