意圖:
用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。
使用場景
- 資源優化場景。
- 類初始化需要消化非常多的資源,這個資源包括數據、硬件資源等。
- 性能和安全要求的場景。
- 通過 new 產生一個對象需要非常繁瑣的數據準備或訪問權限,則可以使用原型模式。
- 一個對象多個修改者的場景。
- 一個對象需要提供給其他對象訪問,而且各個調用者可能都需要修改其值時,可以考慮使用原型模式拷貝多個對象供調用者使用。
- 在實際項目中,原型模式很少單獨出現,一般是和工廠方法模式一起出現,通過 clone 的方法創建一個對象,然後由工廠方法提供給調用者。
複製/克隆
Java中的對象複製/克隆分爲淺複製和深複製:
一、淺複製:
我們知道,一個類的定義中包括屬性和方法。屬性用於表示對象的狀態,方法用於表示對象所具有的行爲。其中,屬性既可以是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 } }
由輸出的結果可以驗證說到的結論。由此我們發現:雖然複製出來的對象重新在堆上開闢了內存空間,但是,對象中各屬性確保持相等。對於基本數據類型很好理解,但對於引用數據類型來說,則意味着此引用類型的屬性所指向的對象本身是相同的, 並沒有重新開闢內存空間存儲。換句話說,引用類型的屬性所指向的對象並沒有複製。
由此,我們將其稱之爲淺複製。當複製後的對象的引用類型的屬性所指向的對象也重新得以複製,此時,稱之爲深複製。
二、深複製:
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 } }
從輸出結果中可以看出,深複製不僅在堆內存上開闢了空間以存儲複製出的對象,甚至連對象中的引用類型的屬性所指向的對象也得以複製,重新開闢了堆空間存儲。