iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 14
0

本文同步分享於個人blog

  • 定義


用原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件。

看完這個定義,大概可以猜到,這個模式的用途是讓我們可以透過複製的方式產生一個新物件。也許有人會說,我要建立物件,new一個不就好了?沒錯,是可以用new的方式建立一個新的物件。但是建立物件這件事是相當耗資源的。而如果在系統中,又需要重複建立相同的物件,勢必會給系統造成很大的負擔。

  • Prototype Pattern 成員


老樣子,在使用Prototype Pattern之前,還是得先認識其中的成員

成員 功用
Prototype 定義實體原型物件必須實作的介面。
ConcretePrototype 實作抽象介面的clone()方法,為可被複製的對象。
Client 使用ConcretePrototype中的clone()來複製新的物件。

Java中就帶有Prototype在的interface Cloneable,效能上比new更加的優良。ConcretePrototype會去實作Prototype的clone(),其中分成淺複製及深複製兩種。最後由Client使用ConcretePrototype完成複製物件的行為。

Prototype Pattern1

  • 淺複製 & 深複製


要實作Prototype Pattern有兩種方法,淺複製(Shallow Clone)和深複製(Deep Clone)。而在複製的資料類型中,又分為基本資料類型(int、double、byte、boolean...等)以及引用類型(class、interface...等)。

淺複製(Shallow Clone)
如果原型物件的成員變數為基本資料類型,將複製一份給新的物件;
如果原型物件的成員變數為引用類型,則只將原本的物件位址複製給新的物件。
也就是原型物件和複製物件的成員變數指向相同的内存位址。

意思就是,淺複製中,只複製了自己本身以及基本資料類型的成員,而引用類型的成員並沒有被複製。

Prototype Pattern2

深複製(Deep Clone)
如果原型物件的成員變數不管是基本資料類型或是引用類型,都會複製給新的物件。

簡單來說,深複製中,除了自己被複製,所有的變數也被複製給新的物件。

Prototype Pattern3

若對兩種複製方式還有興趣可以點這裏參考。

  • Prototype Pattern 實作


多重影分身之術~~~~

相信大家都有看過火影忍者,沒看過的話,可以看一下XD

個人主觀認為,影分身拿來舉這個例子應該滿適合的。鳴人(以下稱拿嚕頭)使用影分身,複製出多個拿嚕頭。藉由影分身修煉的經驗值回到本體身上。他身上的查克拉就很像基本數據類型;而經驗值則是引用類型。所以我們來看看拿嚕頭到底是怎樣使用影分身的。

class NARUTO implements Cloneable {
    NARUTO() {
        System.out.println("NARUTO create success!");
    }
    public Object clone() throws CloneNotSupportedException{
        System.out.println("NARUTO clone success");
        return (NARUTO)super.clone();
    }
}
public class SHADOW_CLONE_JUTSU {
    public static void main(String[] args)throws CloneNotSupportedException
    {
        NARUTO naruto = new NARUTO();
        NARUTO cloneNaruto=(NARUTO)naruto.clone();
        System.out.println("NARUTO == clone NARUTO : " + (naruto==cloneNaruto));
    }
}

output

NARUTO create success
NARUTO clone success
NARUTO == clone NARUTO : false

class NARUTO先實作Cloneable(java.lang.Cloneable,這邊就不多做解釋),然後複寫其內部的clone()。接著clinet將拿嚕頭建立起來後,還需要多個拿嚕頭。就使用NARUTO內的clone,就會有許多的拿嚕頭被複製出來。而這些拿嚕頭在經過比對後,都是一個獨立的物件,並非同一個。

而現在我們要讓影分身也有個地方住,所以我們增加了一個Address的類別。並將那方到NARUTO類別內。

class Address  {  
    private String addr;  
    public String get() {  
        return addr;  
    }  
    public void set(String addr) {  
        this.addr = addr;  
    }  
} 
class NARUTO implements Cloneable {
    private Address addr;  

    NARUTO() {
        System.out.println("NARUTO create success!");
    }
    public Address getAddr() {  
        return addr;  
    }  
    public void setAddr(Address addr) {  
        this.addr = addr;  
    }  
    public Object clone() throws CloneNotSupportedException{
        System.out.println("NARUTO clone success");
        return (NARUTO)super.clone();
    }
}
public class SHADOW_CLONE_JUTSU {
    public static void main(String[] args)throws CloneNotSupportedException
    {
        Address addr = new Address();
        addr.set("Konohagakure");
        NARUTO naruto = new NARUTO();
        naruto.setAddr(addr);
        NARUTO cloneNaruto=(NARUTO)naruto.clone();
        System.out.println("NARUTO address : " + naruto.getAddr().get());
        System.out.println("cloneNaruto address : " + cloneNaruto.getAddr().get());
    }
}

output

NARUTO create success!
NARUTO clone success
NARUTO address : Konohagakure
cloneNaruto address : Konohagakure

一樣我們使用了影分身。乍看之下,好像沒啥不對勁。那如果今天拿嚕頭本體要搬家,他想住到台北。於是我們新增addr.set("Taipei");

public class SHADOW_CLONE_JUTSU {
    public static void main(String[] args)throws CloneNotSupportedException
    {
        Address addr = new Address();
        addr.set("Konohagakure");
        NARUTO naruto = new NARUTO();
        naruto.setAddr(addr);
        NARUTO cloneNaruto=(NARUTO)naruto.clone();
        System.out.println("NARUTO address : " + naruto.getAddr().get());
        System.out.println("cloneNaruto address : " + cloneNaruto.getAddr().get());
        addr.set("Taipei");   // 新增
        System.out.println("NARUTO address : " + naruto.getAddr().get());
        System.out.println("cloneNaruto address : " + cloneNaruto.getAddr().get());
    }
}

output

NARUTO create success!
NARUTO clone success
NARUTO address : Konohagakure
cloneNaruto address : Konohagakure
NARUTO address : Taipei
cloneNaruto address : Taipei

印出來發現,怎們分身也搬到台北了?原因是因為剛剛做的都是淺複製。之前有提到,淺複製對於引用類型不會複製一個到新的物件中。而Address是一個類別,為引用類型,所以如果按照上述的需求,我們必須要用深複製才可以達到目的。

我們先將Address的內容變得可複製,並修改clone內部的實作如下。

class Address implements Cloneable {   // 修改:實作Cloneable
    private String addr;  
    public String get() {  
        return addr;  
    }  
    public void set(String addr) {  
        this.addr = addr;  
    }  
    public Object clone() throws CloneNotSupportedException{  // 新增:複寫clone
        return (Address)super.clone();
    }
} 
class NARUTO implements Cloneable {
    private Address addr;  

    NARUTO() {
        System.out.println("NARUTO create success!");
    }
    public Address getAddr() {  
        return addr;  
    }  
    public void setAddr(Address addr) {  
        this.addr = addr;  
    }  
    public Object clone() throws CloneNotSupportedException{
        System.out.println("NARUTO clone success");
        NARUTO naruto = (NARUTO)super.clone();
        naruto.addr = (Address)addr.clone(); // 新增addr的clone
        return naruto;
    }
}
public class SHADOW_CLONE_JUTSU {
    public static void main(String[] args)throws CloneNotSupportedException
    {
        Address addr = new Address();
        addr.set("Konohagakure");
        NARUTO naruto = new NARUTO();
        naruto.setAddr(addr);
        NARUTO cloneNaruto=(NARUTO)naruto.clone();
        System.out.println("NARUTO address : " + naruto.getAddr().get());
        System.out.println("cloneNaruto address : " + cloneNaruto.getAddr().get());
        addr.set("Taipei");   
        System.out.println("NARUTO address : " + naruto.getAddr().get());
        System.out.println("cloneNaruto address : " + cloneNaruto.getAddr().get());
    }
}

output

NARUTO create success!
NARUTO clone success
NARUTO address : Konohagakure
cloneNaruto address : Konohagakure
NARUTO address : Taipei
cloneNaruto address : Konohagakure

執行後可以發現,本體搬到了台北,分身依舊在木葉。這樣就符合了我們的需求了!!

看完例子覺得,哇!rototypr Pattern好神!!!但他還是存在著一些缺點:
1. 每個類別都需要有clone的方法
2. clone位於類別的內部,當需要做更新時,會直接異動到該類別,違反了OCP

  • 小結


Prototype Pattern的目標
建立原型物件,透過複製的方式建立大量物件,而非傳統的建立方式
Prototype Pattern的成員
Prototype : 定義實體原型物件必須實作的介面。
ConcretePrototype : 實作抽象介面的clone()方法,為可被複製的對象。
Client :  使用ConcretePrototype中的clone()來複製新的物件。
Prototype Pattern的優缺點
優點
1. 效能較佳
2. 隱藏了創建新物件的復雜性
缺點
1. 每個類別都需要有clone的方法
2. clone位於類別的內部,當需要做更新時,會直接異動到該類別,違反了OCP
  • 範例程式碼


範例1:Shallow Clone
範例2:Deep Clone

  • References


Java提高篇——对象克隆(复制)
原型模式(原型设计模式)详解
原型模式 (Prototype Pattern)


上一篇
[Day13] 生成器模式 | Builder Pattern
下一篇
[Day15] 適配器模式 | Adapter Pattern
系列文
從生活中認識Design Pattern30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言