--- title: 'Clone 原型模式' disqus: kyleAlien --- Clone 原型模式 === ## Overview of Content 原型模式 多是使用在,**物件複雜初始化的地方**,複製一個已經存在的實例,可使效率提高 :::success * 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/) 本篇文章對應的是 [**Clone 原型模式 | 解說實現 | Android Framework Intent**](https://devtechascendancy.com/object-oriented_design_clone_framework/) ::: [TOC] ## Clone 使用場景 > 原型模式 多是使用在,**物件複雜初始化的地方**,複製一個已經存在的實例,可使效率提高 * 在類別初始化時消耗過多的資源,原型拷貝可降低消耗量 * 透過 **new 出的一個物件,需要反鎖的過程** > 以下**創立物件的過程** > > > 1. 檢查 Classloader:加載是否已經加載該 Class > > 2. 分配記憶體內存:避免 Mutli Thread 不安全的使用記憶體 > > 3. 內存初始化:清理舊資料 > > 4. 設定 - 對象頭資訊 > > 5. 對象初始化:初始化 static Field、呼叫建後函數 * 一個物件需要給其他類別修改值時,最終才以一個結果決定值時,可以拷貝多個物件給需要的類,這可稱為 **保護性拷貝** :::warning * **要用 Clone 還是 new 來創建對象** ? Clone 並不依定比 new 速度還要快(一般來說是會比 new 快),需要使用者自己考量物件的大小、方便性、使用場景... 等等 > Clone 是透過記憶體二進制流(`Binary stream`)拷貝 ::: ### Clone - 定義 & Clone UML * **Clone 定義** 用原型實例指定創建對象的種類,並且通過 **拷貝原型對象取得新對象** * **Clone UML 模型** | 角色| 功能 | | -------- | -------- | | Cloneable | 標示介面,這種介面內部通常沒有方法,是為了標示該類「擁有」某種特性 | | ConcreateClone | 實作 Cloneable 介面的類,也就是真正要拷貝時會調用到的類 | > ![](https://i.imgur.com/CpAlUpO.png) ### Clone 特色 - 注意事項 1. **不會呼叫建構函數**: 使用 **`clone` 方法 ++不會呼叫建構函數++** 2. **預設淺拷貝**:`clone` 並 **不會複製內部已有的物件**(如 `List`、`Map`... 等等其他物件),所以要 **手動** 針對內部有的物件進行操作 (深拷貝) :::success **淺拷貝只會拷貝,基礎的 8 大類別、String 類... 其他有關 數組、引用對象 是不會拷貝的** ::: * 在 Java 中 [**Cloneable**](https://cs.android.com/android/platform/superproject/+/master:libcore/ojluni/src/main/java/java/lang/Cloneable.java) 是一個 **==標示接口==**,這個接口內並沒有任何方法 ```java= // Cloneable.java public interface Cloneable { } ``` 所謂的 **標示**,代表該接口是用來 **判斷**,像是所有類都隱式繼承 Object 類,Object 內就有一個 clone 方法,在 clone 方法內就有判斷該接口 ```java= // Object.java protected Object clone() throws CloneNotSupportedException { // 判斷該類是否有 Cloneable 接口 if (!(this instanceof Cloneable)) { throw new CloneNotSupportedException("Class " + getClass().getName() + " doesn't implement Cloneable"); } // 透過 Native 複製物件 return internalClone(); } /* * Native helper method for cloning. */ @FastNative private native Object internalClone(); ``` ### Clone - 優缺點 * **拷貝物件不一定比 new 物件速度更快**,需要多加考量,**==原型模式是在 `記憶體` 中++二進制串流的拷貝++==**,**比起 new 可少掉許多步驟** | 優點 | 缺點 | | -------- | -------- | | 唯讀物件可以使用,防止物件被修改 | 它不進行 new 的流程,也就不呼叫建構函數 | | 特別是在一個**需要產生大量物件的地方,可以看出 clone 的好處** | 優點是少了約束,**缺點也是少了約束** | ## Clone 實現 - 淺拷貝 & 深拷貝 如果有寫過 C++,就會對 [**C++ 深淺拷貝**](https://hackmd.io/rYFShCgjTHCcKsaNneFfmA#%E7%89%B9%E6%AE%8A%E6%88%90%E5%93%A1%E5%87%BD%E6%95%B8) 比較有印象,因為必須覆寫 **++指定操作符++、++複製建構函數++ 這兩個方法** ### ShallowClone 淺拷貝 * 預設物件的 clone 是淺拷貝,我們先來寫一個簡單的範例 ```java= public class ShallowClone implements Cloneable { String name; String describe; @Override public ShallowClone clone() { System.out.println("ShallowClone Use clone"); try { // clone 是淺拷貝 return (ShallowClone) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } ShallowClone() { //"1. " 原先建構函數 System.out.println("Make ShallowClone Store"); name = "Java"; describe = "Basic"; } public void print() { System.out.println( "This hashCode: " + hashCode() + "\nName: " + name + ", " + name.hashCode() + "\nDescribe:" + describe + ", " + describe.hashCode()); } } class ShallowMain { public static void main(String[] args) { System.out.println("\nOriginal----------------Create original Object:"); ShallowClone original = new ShallowClone(); original.print(); System.out.println("\nClone----------------Start clone:"); ShallowClone clone = original.clone(); clone.print(); System.out.println("\nClone----------------Change clone member:"); original.name = "Android"; original.describe = "OOP"; clone.print(); System.out.println("\nOriginal----------------After clone change, original Object:"); original.print(); } } ``` **--實作--** > ![](https://i.imgur.com/FyDFKw2.png) 1. **產生不同物件**:透過 Object#clone 函數,確實自身可以產生不同的物件 (透過 hashCode 觀察)、其 member 也可以 :::danger * 請注意!透過 Object#clone 並 **不會觸發建構函數 `constructor`** ::: > ![](https://i.imgur.com/mc2CM5n.png) 2. **複製原物建 member value**:Clone 出來的物件,其 member 會的 Value 都與原來物件指向的位置相同 (這樣也滿符合 clone 的意思) > ![](https://i.imgur.com/Z3foLvY.png) 3. **自己手動設定 Clone 物件的成員**,會發現 **原本指向元物件的位置會改變,變成全新的物件位置** :::success * 這有點類似於 Linux 的 [**Copy on Write**](https://hackmd.io/ptxTEwCzQBuxv61dGL_lvA?view#%E5%AF%AB%E5%85%A5%E6%99%82%E8%A4%87%E8%A3%BD-Copy-on-write---%E9%AB%98%E6%95%88%E8%A1%8C%E7%A8%8B%E5%BB%BA%E7%AB%8B) 技術 ::: ### DeepClone 深拷貝 * 深拷貝就是我們要手動處理當前物件 member 的 clone 行為,新增一個 `ArrayList` 進行拷貝 1. 首先先來看看,未處理引用 member 時,就拷貝的狀況 ```java= public class DeepClone implements Cloneable { String name; String describe; List<Integer> number = new ArrayList<>(); @Override public DeepClone clone() { System.out.println("DeepClone Use clone"); try { return (DeepClone) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } DeepClone() { //"1. " 原先建構函數 System.out.println("Make DeepClone Store"); name = "Java"; describe = "Basic"; number.add(1111); } public void print() { System.out.println( "This hashCode: " + hashCode() + "\nName: " + name + ", " + name.hashCode() + "\nDescribe: " + describe + ", " + describe.hashCode() + "\nNumber: " + number.toString() ); } } class DeepMain { public static void main(String[] args) { System.out.println("\nOriginal----------------Create original Object:"); DeepClone original = new DeepClone(); original.print(); System.out.println("\nClone----------------Start clone:"); DeepClone clone = original.clone(); clone.print(); System.out.println("\nClone----------------Change clone member:"); clone.name = "Android"; clone.describe = "OOP"; clone.number.add(9999); clone.print(); System.out.println("\nOriginal----------------After clone change, original Object:"); original.print(); } } ``` :::warning 從結果可以看出一個重要問題,LinkedList 這個 member 被重複使用。**在 clone 後,添加 List 會 ++影響到原先的物件++** ::: **--實作--** > ![](https://i.imgur.com/1VvtZJl.png) 2. **解決方式**:自己手動處理 clone 方法,解決每個 member 的 clone 問題 ```java= @Override public DeepClone clone() { System.out.println("DeepClone Use clone"); try { DeepClone clone = (DeepClone) super.clone(); clone.name = clone.describe = ""; clone.number = new ArrayList<>(this.number); return clone; } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } ``` **--實作--** > ![](https://i.imgur.com/0GOXBGZ.png) ## Java ### LinkedList 淺拷貝 * Java LinkedList 類,透過自己處理 clone 方法來達成拷貝,我們現在來看看它的 Clone 方式 ```java= public class LinkedListClone implements Cloneable { private String name; private int age; private final LinkedList<String> event = new LinkedList<>(); @Override public LinkedListClone clone() { System.out.println("\n\n LinkedListClone Use clone"); try { // clone 是淺拷貝 return (LinkedListClone) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } LinkedListClone() { //"1. " 原先建構函數 System.out.println("Make LinkedListClone Store"); } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } public void setEvent(String... mem) { for(String m:mem) { if(!event.contains(m)) { event.add(m); } } } public void print() { System.out.println("Name: " + name); System.out.println("Age: " + age); for(String m: event) { System.out.print(m + "... "); } System.out.println(); } } class LinkedListMain { public static void main(String[] args) { LinkedListClone i = new LinkedListClone(); i.setName("Pan"); i.setAge(13); i.setEvent("Hello", "World"); i.print(); LinkedListClone ii = i.clone(); ii.setAge(16); ii.setEvent("Test", "1234"); ii.print(); } ``` **--實作--** > ![](https://i.imgur.com/9CHR6L8.png) * LinkedList 在 clone 後:**將原來 List 的內容,添加到新的 List 中 (LinkedList 有自己處理 clone 方法)**,讓 clone 後的新物件擁有相同的內容 :::danger * 新 LinkedList 加入的成員與舊 LinkedList 相同物件 (HashCode 相同) ::: ```java= // LinkedList.java private LinkedList<E> superClone() { try { return (LinkedList)super.clone(); } catch (CloneNotSupportedException var2) { throw new InternalError(var2); } } public Object clone() { LinkedList<E> clone = this.superClone(); clone.first = clone.last = null; clone.size = 0; clone.modCount = 0; // 在新的 LinkedList 中添加原來物件的內容 for(Node<E> x = this.first; x != null; x = x.next) { clone.add(x.item); } return clone; } ``` > ![](https://i.imgur.com/1tCfzgC.png) ### ArrayList 淺拷貝 * 像是 ArrayList 類就有 Cloneable 界面標示 ```java= // ArrayList.java public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { ... 省略其他方法 public Object clone() { try { ArrayList<?> v = (ArrayList<?>) super.clone(); // Arrays.copyOf 是淺拷貝 v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); } } } ``` * 使用 Arrays.copyOf 方法,其實內部是使用 Array.newInstance 反射創建對象,再使用 System.arraycopy() 複製對象,**但是 ++仍然為淺拷貝++** ```java= // Arrays.java public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) { T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); // 將資料複製到 新 Array 中 System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; } ``` ### Arrays.copyOf 方法 - 淺拷貝 * 當 Arrays.copyOf 使用在數組對象時,改變數組就會改變原來的對象,**代表其實 ==Arrays.copyOf 只是拷貝了數組的位置== (如果修改物件內容,就會直接改到原來的物件)** > 以下範例,使用 `ArrayList#clone`、`Arrays.copyOf` 操作的結果 ```java= import java.util.ArrayList; import java.util.Arrays; public class ArrayCopy_Test { public static void main(String[] args) { Info[] info = {new Info(9999), new Info(6666)}; Info[] copyInfo = Arrays.copyOf(info, info.length); System.out.println("Origin ---------------------------"); showInfo(copyInfo); // 直接修改 copyOf 後的物件 copyInfo[0].mId = 3333; System.out.println("Clone ---------------------------"); showInfo(copyInfo); System.out.println("Origin --------------------------- After clone change"); showInfo(info); } private static void showInfo(Info...infos) { for(int i = 0; i < infos.length; i++) { if(infos[i] == null) { System.out.println("null"); continue; } System.out.println(infos[i].toString()); } System.out.println(); } } class Info { static class Stamp { String signature; } long mId; Stamp stamp; Info(long id) { mId = id; } public Info createStamp(String s) { stamp = new Stamp(); stamp.signature = s; return this; } @Override public String toString() { String base = "id = " + mId; if(stamp != null) { base += (", Stamp = " + stamp.signature); } return base; } } ``` * 從上面兩個範例裡面可以看到它改變了兩次,並且都是透過 **改變 copy 對象的對象就可以影響到原來的對象**,也就證明了 Arrays.copyOf 是複製了對象的地址 **--實作結果--** > ![](https://i.imgur.com/nXraXm5.png) ## Android Soucre ### [Intent](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/content/Intent.java) - 傳遞 * Intent 是四大組件傳遞資料的載體 (Activity、Service、Broadcast ...),**正是因為有 Intent 才能解開四大組件的偶合 (弱耦合)** ```java= private void testIntentClone() { Intent intent = new Intent(); intent.setData(Uri.parse("https:www.google.com")); intent.putExtra("GOOGLE_VERSION", 33); Log.e("TEST123", "\nOriginal: \nData: " + intent.getData() + "\nGOOGLE_VERSION: " + intent.getIntExtra("GOOGLE_VERSION", -1)); Intent cloneOne = new Intent(intent); Log.e("TEST123", "\n Clone: \nData: " + cloneOne.getData() + "\nGOOGLE_VERSION: " + intent.getIntExtra("GOOGLE_VERSION", -1)); } ``` 從結果來看 2 個 Intent 相同 > ![](https://i.imgur.com/cMXberB.png) * Intent 類的 clone 方法會發現 **它並沒有呼叫 `super.clone` 方法**,因為開發者判斷使用 new 成本較低,從這裡可以看出 **==對象拷貝的方案是依照建構成本來考量==** ```java= // Intent.java @Override public Object clone() { // 使用建構函數複製 return new Intent(this); } /** * Copy constructor. */ public Intent(Intent o) { this.mAction = o.mAction; this.mData = o.mData; this.mType = o.mType; this.mPackage = o.mPackage; this.mComponent = o.mComponent; this.mFlags = o.mFlags; this.mContentUserHint = o.mContentUserHint; if (o.mCategories != null) { this.mCategories = new ArraySet<String>(o.mCategories); } if (o.mExtras != null) { this.mExtras = new Bundle(o.mExtras); } if (o.mSourceBounds != null) { this.mSourceBounds = new Rect(o.mSourceBounds); } if (o.mSelector != null) { this.mSelector = new Intent(o.mSelector); } if (o.mClipData != null) { this.mClipData = new ClipData(o.mClipData); } } ``` ## 更多的物件導向設計 物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)! :::info * [**設計建模 2 大概念- UML 分類、使用**](https://devtechascendancy.com/introduction-to-uml-and-diagrams/) * [**物件導向設計原則 – 6 大原則(一)**](https://devtechascendancy.com/object-oriented-design-principles_1/) * [**物件導向設計原則 – 6 大原則(二)**](https://devtechascendancy.com/object-oriented-design-principles_2/) ::: ### 創建模式 - Creation Patterns * [**創建模式 PK**](https://devtechascendancy.com/pk-design-patterns-factory-builder-best/) * **創建模式 - `Creation Patterns`**: 創建模式用於「**物件的創建**」,它關注於如何更靈活、更有效地創建對象。這些模式可以隱藏創建對象的細節,並提供創建對象的機制,例如單例模式、工廠模式… 等等,詳細解說請點擊以下連結 :::success * [**Singleton 單例模式 | 解說實現 | Android Framework Context Service**](https://devtechascendancy.com/object-oriented_design_singleton/) * [**Abstract Factory 設計模式 | 實現解說 | Android MediaPlayer**](https://devtechascendancy.com/object-oriented_design_abstract-factory/) * [**Factory 工廠方法模式 | 解說實現 | Java 集合設計**](https://devtechascendancy.com/object-oriented_design_factory_framework/) * [**Builder 建構者模式 | 實現與解說 | Android Framwrok Dialog 視窗**](https://devtechascendancy.com/object-oriented_design_builder_dialog/) * [**Clone 原型模式 | 解說實現 | Android Framework Intent**](https://devtechascendancy.com/object-oriented_design_clone_framework/) * [**Object Pool 設計模式 | 實現與解說 | 利用 JVM**](https://devtechascendancy.com/object-oriented_design_object-pool/) * [**Flyweight 享元模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_flyweight/) ::: ### 行為模式 - Behavioral Patterns * [**行為模式 PK**](https://devtechascendancy.com/pk-design-patterns-cmd-strat-state-obs-chain/) * **行為模式 - `Behavioral Patterns`**: 行為模式關注物件之間的「**通信**」和「**職責分配**」。它們描述了一系列對象如何協作,以完成特定任務。這些模式專注於改進物件之間的通信,從而提高系統的靈活性。例如,策略模式、觀察者模式… 等等,詳細解說請點擊以下連結 :::warning * [**Stragety 策略模式 | 解說實現 | Android Framework 動畫**](https://devtechascendancy.com/object-oriented_design_stragety_framework/) * [**Interpreter 解譯器模式 | 解說實現 | Android Framework PackageManagerService**](https://devtechascendancy.com/object-oriented_design_interpreter_framework/) * [**Chain 責任鏈模式 | 解說實現 | Android Framework View 事件傳遞**](https://devtechascendancy.com/object-oriented_design_chain_framework/) * [**State 狀態模式 | 實現解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_state/) * [**Specification 規格模式 | 解說實現 | Query 語句實做**](https://devtechascendancy.com/object-oriented_design_specification-query/) * [**Command 命令、Servant 雇工模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_command_servant/) * [**Memo 備忘錄模式 | 實現與解說 | Android Framwrok Activity 保存**](https://devtechascendancy.com/object-oriented_design_memo_framework/) * [**Visitor 設計模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_visitor_dispatch/) * [**Template 設計模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_template/) * [**Mediator 模式設計 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_programming_mediator/) * [**Composite 組合模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_programming_composite/) ::: ### 結構模式 - Structural Patterns * [**結構模式 PK**](https://devtechascendancy.com/pk-design-patterns-proxy-decorate-adapter/) * **結構模式 - `Structural Patterns`**: 結構模式專注於「物件之間的組成」,以形成更大的結構。這些模式可以幫助你確保當系統進行擴展或修改時,不會破壞其整體結構。例如,外觀模式、代理模式… 等等,詳細解說請點擊以下連結 :::danger * [**Bridge 橋接模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_bridge/) * [**Decorate 裝飾模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_decorate/) * [**Proxy 代理模式 | 解說實現 | 分析動態代理**](https://devtechascendancy.com/object-oriented_design_proxy_dynamic-proxy/) * [**Iterator 迭代設計 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_iterator/) * [**Facade 外觀、門面模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_facade/) * [**Adapter 設計模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_adapter/) ::: ## Appendix & FAQ :::info ::: ###### tags: `Java 設計模式` `基礎進階`