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