1. 程式人生 > >【Java設計模式】:建立型模式—原型模式

【Java設計模式】:建立型模式—原型模式

1.原型模式(Prototype Pattern)

定義
原型(Prototype)模式是一種物件建立型模式,他採取複製原型物件的方法來建立物件的例項。使用原型模式建立的例項,具有與原型一樣的資料。

原型模式的特點

  1. 由原型物件自身建立目標物件。也就是說,物件建立這一動作發自原型物件本身。
  2. 目標物件是原型物件的一個克隆。也就是說,通過原型模式建立的物件,不僅僅與原型物件具有相同的結構,還與原型物件具有相同的值。
  3. 根據物件克隆深度層次的不同,有淺度克隆與深度克隆

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. 原型模式的應用場景

最後說一下,原型模式的使用場景

  1. 在建立物件的時候,我們不只是希望被建立的物件繼承其基類的基本結構,還希望繼承原型物件的資料。
  2. 希望對目標物件的修改不影響既有的原型物件(深度克隆的時候可以完全互不影響)。
  3. 隱藏克隆操作的細節,很多時候,對物件本身的克隆需要涉及到類本身的資料細節。
  4. 類初始化需要消化非常多的資源,這個資源包括資料、硬體資源等;
  5. 通過 new 產生一個物件需要非常繁瑣的資料準備或訪問許可權,則可以使用原型模式。
  6. 一個物件需要提供給其他物件訪問,而且各個呼叫者可能都需要修改其值時,可以考慮使用原型模式拷貝多個物件供呼叫者使用。在實際專案中,原型模式很少單獨出現,一般是和工廠方法模式一起出現,通過 clone的方法建立一個物件,然後由工廠方法提供給呼叫者。原型模式先產生出一個包含大量共有資訊的類,然後可以拷貝出副本,修正細節資訊,建立了一個完整的個性物件。