用原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件。
看完這個定義,大概可以猜到,這個模式的用途是讓我們可以透過複製的方式產生一個新物件。也許有人會說,我要建立物件,new一個不就好了?沒錯,是可以用new的方式建立一個新的物件。但是建立物件這件事是相當耗資源的。而如果在系統中,又需要重複建立相同的物件,勢必會給系統造成很大的負擔。
老樣子,在使用Prototype Pattern之前,還是得先認識其中的成員
成員 | 功用 |
---|---|
Prototype | 定義實體原型物件必須實作的介面。 |
ConcretePrototype | 實作抽象介面的clone()方法,為可被複製的對象。 |
Client | 使用ConcretePrototype中的clone()來複製新的物件。 |
Java中就帶有Prototype在的interface Cloneable,效能上比new更加的優良。ConcretePrototype會去實作Prototype的clone(),其中分成淺複製及深複製兩種。最後由Client使用ConcretePrototype完成複製物件的行為。
要實作Prototype Pattern有兩種方法,淺複製(Shallow Clone)和深複製(Deep Clone)。而在複製的資料類型中,又分為基本資料類型(int、double、byte、boolean...等)以及引用類型(class、interface...等)。
如果原型物件的成員變數為基本資料類型,將複製一份給新的物件;
如果原型物件的成員變數為引用類型,則只將原本的物件位址複製給新的物件。
也就是原型物件和複製物件的成員變數指向相同的内存位址。
意思就是,淺複製中,只複製了自己本身以及基本資料類型的成員,而引用類型的成員並沒有被複製。
如果原型物件的成員變數不管是基本資料類型或是引用類型,都會複製給新的物件。
簡單來說,深複製中,除了自己被複製,所有的變數也被複製給新的物件。
若對兩種複製方式還有興趣可以點這裏參考。
多重影分身之術~~~~
相信大家都有看過火影忍者,沒看過的話,可以看一下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 : 定義實體原型物件必須實作的介面。
ConcretePrototype : 實作抽象介面的clone()方法,為可被複製的對象。
Client : 使用ConcretePrototype中的clone()來複製新的物件。
優點
1. 效能較佳
2. 隱藏了創建新物件的復雜性
缺點
1. 每個類別都需要有clone的方法
2. clone位於類別的內部,當需要做更新時,會直接異動到該類別,違反了OCP
範例1:Shallow Clone
範例2:Deep Clone
Java提高篇——对象克隆(复制)
原型模式(原型设计模式)详解
原型模式 (Prototype Pattern)