設計模式之原型模式(Prototype)

意圖:

用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。

使用場景

  1. 資源優化場景。
  2. 類初始化需要消化非常多的資源,這個資源包括數據、硬件資源等。
  3. 性能和安全要求的場景。
  4. 通過 new 產生一個對象需要非常繁瑣的數據準備或訪問權限,則可以使用原型模式。
  5. 一個對象多個修改者的場景。
  6. 一個對象需要提供給其他對象訪問,而且各個調用者可能都需要修改其值時,可以考慮使用原型模式拷貝多個對象供調用者使用。
  7. 在實際項目中,原型模式很少單獨出現,一般是和工廠方法模式一起出現,通過 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

    }
}

從輸出結果中可以看出,深複製不僅在堆內存上開闢了空間以存儲複製出的對象,甚至連對象中的引用類型的屬性所指向的對象也得以複製,重新開闢了堆空間存儲。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章