---
# System prepended metadata

title: 虎年行大運 - Design Pattern - Prototype
tags: [Design Pattern]

---

# 虎年行大運 - 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 原則

![](https://i.imgur.com/rNFqb1Y.png)

*[該圖引用來自 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`