软件设计

OOP 封装:通过 Swift 访问控制理解

学习 OOP 时,“封装”这个词大概会听上百遍,但真被问到“那代码里到底怎么做?”时,往往会突然语塞。概念懂了,手却不知道怎么写。

4 分钟阅读
OOP 封装:通过 Swift 访问控制理解 封面图

学习 OOP 时,“封装”这个词大概会听上百遍,但真被问到“那代码里到底怎么做?”时,往往会突然语塞。概念懂了,手却不知道怎么写。

先说结论,封装就是“隐藏对象内部数据,防止外部随意修改,只允许通过规定的路径访问”。在 Swift 中,它通过访问修饰符实现。

今天我们会用实际代码逐一查看 private、internal 等 5 个访问控制级别,并整理信息隐藏为何能保护代码。

封装到底是什么?

封装(Encapsulation)是 OOP 四大特征之一。

用一句话概括:

将数据与处理数据的功能绑定在一起,隐藏内部实现,只向外部开放必要部分。

为什么要隐藏?

想想银行账户就容易理解了。如果任何人都能直接把余额改成任意数字,后果会很严重。

存款和取款必须经过规定流程,并确保“不能取出超过余额的金额”等规则得到遵守。

像这样阻止直接操作内部值,只开放经过验证的路径,就叫作信息隐藏

如果说封装是更大的概念,那么信息隐藏就是实现它的核心原则。


Swift 五种访问控制级别对比一览

Swift 将访问范围分为 5 个级别,下面按范围从窄到宽整理。

(以 2026 年 Swift 最新语法为准。)

访问修饰符 可访问范围 主要用途
private 声明所在的大括号块内部 真正想隐藏的内部状态
fileprivate 整个相同源文件 同一文件中的协作类型
internal 整个相同模块(应用/框架) 默认值,大多数代码
public 也可从其他模块使用 库对外公开的 API
open 允许从其他模块继承和重写 框架扩展点

这里有一点一定要记住。

什么都不加时,internal就是默认值。

所以平时即使不写访问修饰符,在同一个应用内也能正常运行。

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**在创建单个应用时大多都适用,不必全部显式写出。
  • **public·open**只有在创建供他人使用的库或框架时才需要考虑。
  • 只有需要开放继承时才使用**open。**否则public就足够了。
情况 判断
只在内部使用的状态变量 private
只想让外部读取 private(set)
在同一应用内自由使用 internal(默认值)
作为库对外公开 public
允许外部继承和重写 open

只需记住一点:访问范围始终应尽可能小地开放。


面试中会这样问

Q. 封装和信息隐藏有什么区别?

封装是将数据和方法绑定到同一个对象中的设计概念,而信息隐藏是将内部实现隐藏起来的原则。简洁地说,封装是更大的容器,信息隐藏则通过访问修饰符在其中实现。

Q. Swift 中 public 和 open 有什么区别?

两者都可以从其他模块访问,但只有open允许外部模块继承和重写方法。public类可以在外部使用,但不能被继承。补充说明只有在开放框架扩展点时才使用open即可。


访问修饰符看起来像是记住几个语法规则就够了,但实际上是在训练你设计“谁可以修改这个值”。

亲自敲一遍今天的账户示例,马上就能掌握。推荐在小型项目中先从private开始添加!

延伸阅读