---
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 介面的類,也就是真正要拷貝時會調用到的類 |
> 
### 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();
}
}
```
**--實作--**
> 
1. **產生不同物件**:透過 Object#clone 函數,確實自身可以產生不同的物件 (透過 hashCode 觀察)、其 member 也可以
:::danger
* 請注意!透過 Object#clone 並 **不會觸發建構函數 `constructor`**
:::
> 
2. **複製原物建 member value**:Clone 出來的物件,其 member 會的 Value 都與原來物件指向的位置相同 (這樣也滿符合 clone 的意思)
> 
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 會 ++影響到原先的物件++**
:::
**--實作--**
> 
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;
}
```
**--實作--**
> 
## 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();
}
```
**--實作--**
> 
* 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;
}
```
> 
### 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 是複製了對象的地址
**--實作結果--**
> 
## 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 相同
> 
* 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 設計模式` `基礎進階`