# 虎年行大運 - Design Pattern - Prototype
## 前言
身為工程師的你,是不是偶爾會面對到以下幾種狀況?
1. 需要一個相同的物件來執行事務上的處理,且不能透過 new,因為須要當前物件狀態。
2. 正在跑 Loop 建立大量、相同的物件,差異僅在物件中的屬性值,但建立物件的過程涉及了十幾道工。
曾經的作法就是暴力重建、暴力複製、富含耐心的等待...
結果好死不死發現物件中有些屬性是 Private 無法進行暴力複製;或者僅知道介面,無法找到想複製的實體類別物件...欲哭無淚。
不需要這麼累,也不需要這麼耗時間與資源,有了今天要介紹的方法,省下的時間可以早點下班跟家人或愛人團圓~
## 主題
**Prototype Pattern**
中文常見名稱 **原型模式**
屬於 Creational Pattern 的一種,意在透過 Clone 物件的方式,保留當前物件狀態與屬性、減少建立物件的過程與時間。
Java 中的複製還包含兩種: **淺複製** 與 **深複製**
在談及兩種複製之前,還要先認識到 Java 的物件中通常包含兩種類型的屬性: **原始型別屬性**、**類別屬性(引用類型屬性)**
- **淺複製**
- 若成員屬性為**原始型別**,如: int, double, String 等,**會複製**實體給新物件
- 若成員屬性為**類別**,如: Collection 類,**不會複製**實體,會引用同一來源
- **深複製**
- 若成員屬性為**原始型別**,如: int, double, String 等,**會複製**實體給新物件
- 若成員屬性為**類別**,如: Collection 類,**會複製**實體給新物件
**優點:**
- 執行效能佳 (new 的建立物件方式很耗費資源)
- 節省建立時間
- 可取得無法透過 new 方式建立的物件實體
**缺點:**
- 每一個類別屬性都需要實作 Cloneable 介面 (意即每個類別都要有 clone())
- 若需要更動 clone() 時,會直接對類別物件進行修改,違背 OCP 原則

*[該圖引用來自 Wiki Prototype Pattern - Class Diagram](https://en.wikipedia.org/wiki/Prototype_pattern)*
**架構圖物件說明**
| 項目 | 說明 |
| ---- | ---- |
| Prototype | 介面或抽象,須實作 Cloneable 介面 |
| ConcretePrototype1 | 介面實體類,若屬性有類別,則需要覆寫 clone() 並在其中將類別成員屬性一併 clone 後再引用 |
| Client | 負責使用 Prototype 的物件 |
## 實行方式
```java=
/**
* @author kaichen
* 透過 Clone 方式得到物件,可以避免無法透過 new 建立物件或是建立物件的過程非常複雜等情境。
* 注意,在物件中的類別成員屬性也需要實作 Cloneable 介面,並在物件的 Clone() 中添加該類別成員屬性的 clone 和更新,否則當類別成員屬性會引用同一來源
* 注意,在物件中的原始型別成員屬性因為是 Immutable Object 的關係,因此就算改變賦予的值,也會直接生成新物件,而不影響原先物件引用的來源
*/
public class Prototype {
public static void main(String [] args) throws CloneNotSupportedException{
/* 設定放入參數 */
int testI = 10;
String testStr = "10";
TestClass testClass = new TestClass("test10", "test10");
/* 建立物件和 Clone物件 */
PrototypeAbs prototypeAbs = new ConcretePrototype1(testI, testStr, testClass);
PrototypeAbs clonePrototypeAbs = prototypeAbs.clone();
PrototypeAbs prototypeAbs2 = new ConcretePrototype2(testI, testStr, testClass);
PrototypeAbs clonePrototypeAbs2 = prototypeAbs2.clone();
/* 設定新值給參數 */
/* 原始型別屬性會直接替新值建立新物件,不影響物件原先引用來源 */
/* 類別型別屬性因沒有在 Clone() 中一併複製關係,造成兩物件引用同一來源,因此改變類別型別屬性的值,會同時影響兩物件 */
testI = 20;
testStr = "20";
testClass.setTestStr1("test20");
testClass.setTestStr2("test20");
System.out.println("PrototypeAbs 1");
System.out.println(prototypeAbs.hashCode() + " Get Int: " + prototypeAbs.getI());
System.out.println(prototypeAbs.hashCode() + " Get String: " + prototypeAbs.getStr());
System.out.println(prototypeAbs.hashCode() + " Get TestClass String 1: " + prototypeAbs.getTestClass().getTestStr1());
System.out.println(prototypeAbs.hashCode() + " Get TestClass String 2: " + prototypeAbs.getTestClass().getTestStr2());
System.out.println();
System.out.println("PrototypeAbs 1 Clone");
System.out.println(clonePrototypeAbs.hashCode() + " Get Int: " + clonePrototypeAbs.getI());
System.out.println(clonePrototypeAbs.hashCode() + " Get String: " + clonePrototypeAbs.getStr());
System.out.println(clonePrototypeAbs.hashCode() + " Get TestClass String 1: " + clonePrototypeAbs.getTestClass().getTestStr1());
System.out.println(clonePrototypeAbs.hashCode() + " Get TestClass String 2: " + clonePrototypeAbs.getTestClass().getTestStr2());
System.out.println();
System.out.println("PrototypeAbs 2");
System.out.println(prototypeAbs2.hashCode() + " Get Int: " + prototypeAbs2.getI());
System.out.println(prototypeAbs2.hashCode() + " Get String: " + prototypeAbs2.getStr());
System.out.println(prototypeAbs2.hashCode() + " Get TestClass String 1: " + prototypeAbs2.getTestClass().getTestStr1());
System.out.println(prototypeAbs2.hashCode() + " Get TestClass String 2: " + prototypeAbs2.getTestClass().getTestStr2());
System.out.println();
System.out.println("PrototypeAbs 2 Clone");
System.out.println(clonePrototypeAbs2.hashCode() + " Get Int: " + clonePrototypeAbs2.getI());
System.out.println(clonePrototypeAbs2.hashCode() + " Get String: " + clonePrototypeAbs2.getStr());
System.out.println(clonePrototypeAbs2.hashCode() + " Get TestClass String 1: " + clonePrototypeAbs2.getTestClass().getTestStr1());
System.out.println(clonePrototypeAbs2.hashCode() + " Get TestClass String 2: " + clonePrototypeAbs2.getTestClass().getTestStr2());
}
}
abstract class PrototypeAbs implements Cloneable{
protected int i = 0;
protected String str = "0";
protected TestClass testClass = null;
public PrototypeAbs (){}
public PrototypeAbs(int newI, String newStr, TestClass newTestClass){
this.i = newI;
this.str = newStr;
this.testClass = newTestClass;
}
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
public TestClass getTestClass() {
return testClass;
}
public void setTestClass(TestClass testClass) {
this.testClass = testClass;
}
@Override
public PrototypeAbs clone() throws CloneNotSupportedException {
return (PrototypeAbs) super.clone();
}
}
class ConcretePrototype1 extends PrototypeAbs {
public ConcretePrototype1 (){}
public ConcretePrototype1(int newI, String newStr, TestClass newTestClass){
super(newI, newStr, newTestClass);
}
@Override
public PrototypeAbs clone() throws CloneNotSupportedException {
ConcretePrototype1 c = (ConcretePrototype1) super.clone();
TestClass t = (TestClass) super.testClass.clone();
c.setTestClass(t);
return c;
}
}
class ConcretePrototype2 extends PrototypeAbs {
public ConcretePrototype2 (){}
public ConcretePrototype2(int newI, String newStr, TestClass newTestClass){
super(newI, newStr, newTestClass);
}
}
class TestClass implements Cloneable {
private String testStr1 = "testStr";
private String testStr2 = "testStr";
public TestClass(){}
public TestClass(String testStr1, String testStr2){
this.testStr1 = testStr1;
this.testStr2 = testStr2;
}
public void setTestStr1(String newStr){
this.testStr1 = newStr;
}
public void setTestStr2(String newStr){
this.testStr2 = newStr;
}
public String getTestStr1(){
return this.testStr1;
}
public String getTestStr2(){
return this.testStr2;
}
@Override
public TestClass clone() throws CloneNotSupportedException {
return (TestClass) super.clone();
}
}
```
**實行結果**
```
有實作類別 clone 的物件,其類別屬性的引用來源已不同 - 深複製
PrototypeAbs 1
1882008996 Get Int: 10
1882008996 Get String: 10
1882008996 Get TestClass String 1: test20
1882008996 Get TestClass String 2: test20
PrototypeAbs 1 Clone
638559109 Get Int: 10
638559109 Get String: 10
638559109 Get TestClass String 1: test10
638559109 Get TestClass String 2: test10
------------------------------------------------
無實作類別 clone 的物件,其類別屬性的引用來源相同 - 淺複製
PrototypeAbs 2
1287160904 Get Int: 10
1287160904 Get String: 10
1287160904 Get TestClass String 1: test20
1287160904 Get TestClass String 2: test20
PrototypeAbs 2 Clone
1710537297 Get Int: 10
1710537297 Get String: 10
1710537297 Get TestClass String 1: test20
1710537297 Get TestClass String 2: test20
```
上述例子中共有兩個實體類繼承了 PrototypeAbs 抽象。分成有覆寫 clone 的**深複製**與沒覆寫 clone 的**淺複製**,在最後的結果輸出很清楚的顯示: 有覆寫的,其類別成員屬性的值已引用不同的來源;沒覆寫的,其類別成員屬性的值依舊是相同來源。
首頁 [Kai 個人技術 Hackmd](/2G-RoB0QTrKzkftH2uLueA)
###### tags: `Design Pattern`