軟體設計

OOP 封裝:用 Swift 存取控制理解

學習 OOP 時,「封裝」這個詞大概會聽上百次,但真的被問到「所以程式碼要怎麼做?」時,卻常常突然說不出話。概念明白,手卻動不了。

閱讀 4 分鐘
OOP 封裝:用 Swift 存取控制理解 封面圖

學習 OOP 時,「封裝」這個詞大概會聽上百次,但真的被問到「所以程式碼要怎麼做?」時,卻常常突然說不出話。概念明白,手卻動不了。

先說結論,封裝就是「隱藏物件內部資料,避免外部任意修改,只能透過既定管道存取」。在 Swift 中,這是透過存取修飾詞實作的。

今天會用實際程式碼逐一看看 private、internal 等 5 個層級的存取控制,整理資訊隱藏為什麼能保護程式碼。

封裝到底是什麼?

封裝(Encapsulation)是 OOP 四大特性之一。

用一句話總結如下。

將資料與處理資料的功能綁在一起,隱藏內部實作,只向外公開必要部分。

為什麼要隱藏?

想想銀行帳戶就很容易理解。如果任何人都能直接把帳戶餘額改成任意數字,事情就嚴重了。

必須經過存款與提款等既定流程,並確保「不能提領超過餘額」之類的規則確實被遵守。

像這樣阻止外部直接碰觸內部值,只開放經過驗證的管道,就稱為資訊隱藏

如果封裝是較大的概念,資訊隱藏就是實現它的核心原則。


Swift 存取控制 5 個層級一覽比較

Swift 將存取範圍分成 5 個層級,以下依範圍由窄到寬整理。

(以 2026 年 Swift 最新語法為準。)

存取修飾詞 可存取範圍 主要用途
private 宣告所在的大括號區塊內 真正想隱藏的內部狀態
fileprivate 整個相同原始程式檔 同一檔案中的協作型別
internal 整個相同模組(App/Framework) 預設值,大多數程式碼
public 也能從其他模組使用 函式庫對外公開的 API
open 允許從其他模組繼承與覆寫 Framework 擴充點

這裡有一點一定要記住。

什麼都不加時,internal就是預設值。

所以平常即使不寫存取修飾詞,在同一個 App 裡也能正常運作。

publicopen的差異也很容易混淆。在其他模組中,只有open可以進行繼承與覆寫public雖然可以使用,但不能繼承。

只加上一個 private,程式碼就可靠得多
只加上一個 private,程式碼就可靠得多

用 private 實現真正的資訊隱藏

光用文字不容易體會,我們直接看帳戶範例的程式碼。

先從沒有封裝的錯誤範例開始。

class BadAccount {
    var balance: Int = 0   // 外部任何人都能修改
}

let acc = BadAccount()
acc.balance = -99999      // 不合理的值也會直接寫入
print(acc.balance)
// 輸出: -99999

餘額竟然是負 9 萬,規則整個崩潰了。

現在用private隱藏內部狀態,只開放存提款管道。

class Account {
    private var balance: Int = 0        // 阻擋外部存取

    func deposit(_ amount: Int) {
        guard amount > 0 else { return }
        balance += amount
    }

    func withdraw(_ amount: Int) -> Bool {
        guard amount > 0, balance >= amount else { return false }
        balance -= amount
        return true
    }

    var currentBalance: Int { balance }  // 只允許讀取
}

let myAccount = Account()
myAccount.deposit(10000)
print(myAccount.withdraw(30000))  // 餘額不足
// 輸出: false

現在外部無法直接修改balance

myAccount.balance = -99999這樣的程式碼會直接產生編譯錯誤。

資金只能透過depositwithdraw流動,驗證規則也明確寫在其中。

這就是資訊隱藏的力量:從根本上阻止你寫出違反規則的程式碼。


private(set) — 開放讀取,只禁止寫入

接著來看更實務的情況。

「外部可以讀取值,但只想禁止修改」是非常常見的需求。

像上面一樣另外建立計算屬性當然可以,但 Swift 提供了更簡潔的方法。

class ScoreBoard {
    private(set) var score: Int = 0   // 讀取 public, 寫入 private

    func addPoint() {
        score += 10
    }
}

let board = ScoreBoard()
board.addPoint()
print(board.score)   // 讀取不受限制
// 輸出: 10
// board.score = 999  // 這行會產生編譯錯誤

加上private(set)後,讀取仍對外開放,但寫入權限會限制在內部。

分數只能透過addPoint()增加,因此能避免遊戲邏輯外部任意操控分數。

我很常用這個語法,因為不需要另外建立計算屬性。

外部只能透過存提款管道存取,餘額則牢牢藏在內部
外部只能透過存提款管道存取,餘額則牢牢藏在內部

什麼時候該用哪個修飾詞?

整理幾個實務上感到困惑時可以依循的判斷標準。

  • **先從private**開始。需要時再擴大範圍,比一開始全部開放、之後再縮小更安全。
  • **預設值internal**在建立單一 App 時大多都很合適,不必全部明確寫出來。
  • **publicopen**只有在建立供他人使用的函式庫或 Framework 時才需要考慮。
  • 只有在要開放繼承時才使用**open。**否則使用public就足夠了。
情況 判斷
只在內部使用的狀態變數 private
只想讓外部讀取 private(set)
在同一個 App 內自由使用 internal(預設值)
以函式庫形式對外公開 public
允許外部繼承與覆寫 open

只要記住一件事:存取範圍總是應該盡可能縮小後再開放。


面試時會這樣問

Q. 封裝和資訊隱藏有什麼不同?

封裝是將資料與方法綁定在同一個物件中的設計概念;資訊隱藏則是將內部實作隱藏於外部之外的原則。簡潔地說,封裝是較大的容器,而資訊隱藏則透過存取修飾詞在其中實現。

Q. Swift 中 public 和 open 有什麼差異?

兩者都能從其他模組存取,但只有open允許外部模組繼承與覆寫方法。public類別可以在外部使用,但不能被繼承。補充說明只有在開放 Framework 擴充點時才使用open即可。


存取修飾詞看似只是幾個記住就好的語法,但其實是在訓練你設計「誰可以碰這個值」。

只要親自打一遍今天的帳戶範例,應該就能豁然開朗。推薦在小型專案中先從private開始加起!

延伸閱讀