ソフトウェア設計

OOPのカプセル化:Swiftのアクセス制御で理解する

OOPを学んでいると「カプセル化」という言葉を何度も耳にしますが、「ではコードではどう実装するの?」と聞かれると、急に言葉に詰まることがあります。概念は分かっていても、手が動かないのです。

読了 6 分
OOPのカプセル化:Swiftのアクセス制御で理解するのカバー画像

OOPを学んでいると「カプセル化」という言葉を何度も耳にしますが、「ではコードではどう実装するの?」と聞かれると、急に言葉に詰まることがあります。概念は分かっていても、手が動かないのです。

結論から言うと、カプセル化とは「オブジェクト内部のデータを外部から勝手に変更できないよう隠し、決められた経路からだけアクセスさせること」です。Swiftではこれをアクセス修飾子で実装します。

今日はprivateやinternalなど、5段階のアクセス制御を実際のコードで一つずつ見ながら、情報隠蔽がなぜコードを守るのか整理します。

カプセル化とは何か?

カプセル化(Encapsulation)はOOPの4大特性の一つです。

一言でまとめると、こうなります。

データとそれを扱う機能を一つにまとめ、内部は隠し、必要な部分だけを外部に公開すること。

なぜ隠すのでしょうか?

銀行口座を思い浮かべると分かりやすいでしょう。誰でも残高を好きな数字に直接変更できたら大変です。

入金と出金という決められた手続きを通し、「残高以上は引き出せない」といったルールを守る必要があります。

このように内部の値を直接操作できないようにし、検証済みの経路だけを開くことを情報隠蔽と呼びます。

カプセル化が大きな概念だとすれば、情報隠蔽はそれを実現する中心原則です。


Swiftのアクセス制御5段階を一覧で比較

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**は、アプリ一つを作る場合の大半に適しています。すべて明示する必要はありません。
  • **publicopen**は、他者が使うライブラリやフレームワークを作るときだけ検討すれば十分です。
  • 継承まで開放するときだけ**openを使います。**それ以外はpublicで十分です。
状況 判断
内部でのみ使う状態変数 private
外部には読み取りだけ許可したい private(set)
同じアプリ内で自由に internal(デフォルト)
ライブラリとして外部公開 public
外部からの継承・オーバーライドを許可 open

一つだけ覚えてください。アクセス範囲は常にできるだけ狭く開くのが正解に近いです。


面接ではこう聞かれます

Q. カプセル化と情報隠蔽の違いは何ですか?

カプセル化はデータとメソッドを一つのオブジェクトにまとめる設計概念で、情報隠蔽は内部実装を外部から隠す原則です。カプセル化が大きな器で、その中で情報隠蔽をアクセス修飾子によって実現すると答えれば簡潔です。

Q. Swiftのpublicとopenの違いは?

どちらも別モジュールからアクセスできますが、外部モジュールで継承とメソッドのオーバーライドが許可されるのはopenだけです。publicクラスは外部で利用できますが、継承はできません。フレームワークの拡張ポイントを開く場合だけopenを使うと補足するとよいでしょう。


アクセス修飾子は、いくつかの構文を覚えれば終わりに見えます。しかし実際には、「この値を誰が変更してよいのか」を設計する思考訓練です。

今日の口座例を一度自分で入力してみると、感覚がつかめるはずです。小さなプロジェクトでまずprivateを付けてみることをおすすめします!

あわせて読みたい