先學過 Objective-C 或 Java,再轉向 Swift 時,一定會遇到一個卡關的地方。
那就是「要如何複製物件?」這個問題。
很容易到處尋找像 clone() 這樣的方法,但在 Swift 中幾乎不需要這麼做。
先說結論。
Swift 藉由結構和陣列等值型別,以及 Copy-on-Write(延遲複製)最佳化,在語言層級取代了設計模式中的「原型模式」。
不必另外設計複製物件的模式,只要指派值,就能取得安全的複本。
本文會搭配範例整理原型模式原本要解決的問題,以及 Swift 的值型別和 Copy-on-Write 如何自然地承接這項工作。
要區分複製的種類,首先需要以 淺層複製 vs 深層複製 為基礎。若還需要複製類別,Swift 原型模式與 NSCopying 則展示另一種選擇。
原型模式原本要解決的問題
原型模式屬於 GoF 設計模式中的建立型模式。
核心很簡單:不從頭建立新物件,而是複製既有物件來取得新的執行個體。
為什麼需要這種做法?
在 Objective-C 和 Java 等語言中,物件大多是參考型別。
將物件指派給變數時,複製的不是值,而是指向同一物件的位址。
因此修改 A 時,B 也會一起被修改。
為了避免這種情況,開發者會自行建立 clone() 或 copy() 方法,讓它回傳「真正的複本」。
原型模式就是用來處理這種情況的工具。
- 物件建立成本很高,但需要多個相似物件時
- 需要不修改原物件的獨立複本時
- 希望由物件自行負責複製邏輯時
Swift 值型別:只要指派就完成複製
Swift 的結構(struct)與列舉(enum),以及 Array、Dictionary、String 等標準型別,全都是值型別。
值型別在指派或傳入函式的瞬間就會被複製。
換句話說,語言會自動替你建立複本。
看範例會清楚得多。
struct Point { var x: Int; var y: Int }
var a = Point(x: 1, y: 2)
var b = a // 此刻值就會被複製
b.x = 99
// a.x仍然只 1, b.x 99
b = a這一行就取代了原型模式中clone()所負責的工作。
不需要另外建立複製方法,也不需要採用複製協定。
原始的 a 會受到安全保護,而 b 會成為完全獨立的複本。
物件彼此糾纏所造成的棘手錯誤,從結構上就不可能發生。
Copy-on-Write 解決效能問題的方式
這裡自然會產生一個疑問。
「每次指派都整份複製的話,大型陣列不會變得太慢嗎?」
這個指摘很合理。因此 Swift 使用的最佳化就是 Copy-on-Write,簡稱 CoW。
CoW 的原理如下。
指派值時,會先共用內部儲存空間,延後實際資料複製。
直到其中一方要修改值的瞬間,才會真正進行複製。
| 時機 | 內部運作 | 成本 |
|---|---|---|
| 指派時 | 共用儲存空間(只有參照) | 非常低 |
| 只有讀取時 | 持續維持共用 | 不需複製 |
| 修改值時 | 此時才實際複製 | 只在此時發生 |
因此開發者能享有值型別的安全性,也不必付出不必要的複製成本。
只讀取而不修改時,根本不會發生複製。
Array、Dictionary、Set、String 等標準程式庫型別早已內建這項 CoW。
即使不自行實作,語言和標準程式庫也會自動處理。
那麼,還有需要使用原型模式的情況嗎?
「那麼在 Swift 中,原型模式已經完全過時了嗎?」
也不一定。
有些情況必須使用類別(class),例如需要參考語意、必須與 Objective-C 互通,或需要繼承。
這時若需要類別執行個體的獨立複本,仍然必須自行建立複製邏輯。
Swift 提供 NSCopying 協定與 copy(),這基本上就是原型模式的傳統形式。
可以這樣區分,會比較容易理解。
- 使用結構與值型別 → 只要指派即可完成複製,不需要原型模式
- 必須使用類別 → 必要時自行實作複製邏輯,模式在此存續
因此 Swift 社群經常建議「優先考慮值型別」。
因為以值型別為預設,就能消除複製問題本身。
總結
將不熟悉語言的設計模式硬套進來,反而常常會讓程式碼變得複雜。
只要記住在 Swift 中,值型別與 Copy-on-Write 會自然填補原型模式的位置,就能大幅減輕對複製的煩惱。
下次需要複製物件時,在尋找 clone() 前,先想想「這個能不能改成結構?」一定會有所幫助。

