LASSIC Media らしくメディア

2026.10.09 らしくコラム

カプセル化とは?データを勝手に書き換えさせない設計の考え方

監修・編集責任者:牛尾 昭昌(株式会社LASSIC 執行役員)

黒い背景のエディタに、構造体や変数の定義が色付きで並ぶプログラムのコードを斜めから写した写真。

この記事の結論

  • カプセル化は、内部の状態を外から直接変えさせず、決めた操作だけを通すことで、不変条件を1か所で守る設計の考え方です。
  • 元にある情報隠蔽は、変わりそうな設計上の決定をモジュールの内側に隠し、変更が呼び出し側に広がらないようにする考え方です。
  • アクセス修飾子の強さは言語で違い、検査の無いsetterや内部のリストを返すgetterでは、privateにしても条件は守れません。

※ 本記事は2026年10月時点の公式情報(省庁・公的機関、および製品やサービスを提供する事業者が公開している資料)に基づきます。

カプセル化は、オブジェクトの内部の状態を外から直接触らせず、決めた操作を通してだけ変えさせる設計の考え方です。オブジェクト指向の入門で早い段階に出てくる用語ですが、「変数をprivateにしてgetterとsetterを付けること」と覚えられやすく、何を守るための仕組みなのかが抜け落ちがちです。

本記事では、システム開発に携わるエンジニアやPMの方に向けて、David Parnasの1972年の論文と、Java・Python・C#・TypeScriptの公式ドキュメントをもとに、カプセル化の目的と仕組みを整理します。Pythonの短いコードで、公開属性の書き換えで不変条件が壊れる例とメソッド経由で守る例を示し、getterとsetterの落とし穴にも触れます。

カプセル化とは

Oracle のJavaチュートリアルは、オブジェクトは状態をフィールドに持ち、振る舞いをメソッドとして外に見せるものだと説明しています。そのうえで、内部の状態を隠し、外とのやり取りをすべてメソッド経由にすることをデータのカプセル化と呼び、オブジェクト指向プログラミングの基本原則の1つと位置づけています。*1

同じページでは自転車を例に挙げています。変速が6段しかないなら、ギアを変えるメソッドは1未満や6を超える値を拒否できます。状態を変える入口をメソッドに限るので、外からどう使われるかをオブジェクト自身が管理できる、という説明です。あわせて、メソッドだけを通してやり取りすれば内部の実装の細部が外から隠れる、という情報隠蔽の利点も挙げています。*1

つまりカプセル化には、正しい状態(不変条件)を守る働きと、内部の作りを隠して後から変えても呼び出し側に響かないようにする働きがあります。後者をさかのぼると、Parnasの論文に行き着きます。

情報隠蔽との関係

Parnasは1972年にCommunications of the ACMに載った論文「On the Criteria To Be Used in Decomposing Systems into Modules」で、システムをモジュールに分けるときの基準を論じました。*2 題材はKWICインデックスで、入力した各行の語を1語ずつずらした行を作り、アルファベット順に並べて出力するシステムです。

論文は2つの分け方を比べています。1つ目は、入力・循環シフト・整列・出力という処理の手順ごとにモジュールを分けたものです。行を記憶領域にどう格納するかという形式を全部のモジュールが使うため、格納の形式を変えると全部のモジュールに変更が及びます。2つ目は、行の格納を受け持つモジュールを置き、ほかのモジュールには関数を通してだけ文字を読み書きさせるもので、格納のやり方はそのモジュール以外から完全に隠れています。

行の格納形式を変えたときに変更が及ぶ範囲の比較図。左は処理の手順(入力・循環シフト・整列・出力)で分けた場合で、全モジュールが行の格納形式を直接使うため、格納形式を変えるとすべてのモジュールに変更が及ぶ。右は設計上の決定を隠して分けた場合で、行の格納モジュールが関数(CHAR・SETCHAR・WORDS)だけを公開し、格納形式はそのモジュールの中だけが知るため、直すのは格納モジュールだけで済む。整列と出力は循環シフトの関数を通して行を読む。D. L. Parnasの1972年の論文のKWICインデックスの例をもとに作成(主制御のモジュールは省略)。

Parnasは、2つ目の分け方の各モジュールは、ほかのすべてから隠す設計上の決定によって特徴づけられ、インターフェースは内部をできるだけ明かさないように選ばれた、と述べています。結論では、フローチャートからモジュールを切り出すのではなく、難しい設計上の決定や変わりそうな決定の一覧から始め、各モジュールがその決定をほかから隠すように設計することを提案しました。*2

論文には、隠し方が足りなかった点への反省も書かれています。循環シフトのモジュールは、一覧の格納や計算のやり方は隠したものの、一覧の並び順まで決めてしまったため、必要以上の情報を明かし、定義を変えずに作れるシステムの幅を狭めた、というものです。*2 何を公開するかは、アクセス修飾子の付け方より先に、どの決定を隠すかで決まるということが読み取れます。

不変条件を守る仕組み

不変条件とは、オブジェクトがいつ見ても満たしているべき条件のことです。口座なら「残高は0以上」、注文なら「合計金額は明細の金額の合計と一致する」といったものが当たります。次のコードは、残高を公開属性のまま持つ口座です。出金のメソッドは残高不足を検査していますが、呼び出し側が属性を直接書き換えると、その検査を通りません。

# 残高を公開属性のまま持つ口座(不変条件:残高は0以上)
class Account:
    def __init__(self, balance):
        self.balance = balance

    def withdraw(self, amount):
        if amount > self.balance:
            raise ValueError("残高不足")
        self.balance -= amount

a = Account(1000)
a.balance -= 1500          # 呼び出し側が直接書き換えると検査を通らない
print(a.balance)           # -500

実行すると -500 と表示され、残高は0以上という条件が崩れます。問題は出金のメソッドの中身ではなく、状態を変える入口がメソッドの外にも開いていることです。次は、残高を _balance に置き、読み取りは property、変更は withdraw() だけに通した形です。

# 残高は _balance に隠し、変更はメソッドだけに通す
class Account:
    def __init__(self, balance):
        if balance < 0:
            raise ValueError("残高は0以上")
        self._balance = balance

    @property
    def balance(self):         # 読み取り専用(setter を定義しない)
        return self._balance

    def withdraw(self, amount):
        if not 0 < amount <= self._balance:
            raise ValueError("出金額が不正")
        self._balance -= amount

a = Account(1000)
a.withdraw(300)
print(a.balance)           # 700
a.balance = -500           # AttributeError になる

実行すると、出金後の残高として 700 が表示され、最後の行の代入は setter が無いため AttributeError になりました。Pythonの公式ドキュメントも、@property をデコレーターとして使えば読み取り専用のプロパティを簡単に作れると説明しています。*5 初期化のときにも同じ条件を検査しているので、このクラスの外から負の残高の口座は作れません。条件の検査を1か所に集められることが、カプセル化の実務での大きな利点です。

ただし、Pythonの _balance は慣習で隠しているだけで、a._balance = -500 と書けば書き換えられます。どこまで強く隠せるかは言語によって違います。

言語ごとのアクセス修飾子

主な言語のアクセス制御の違い(Javaは*3、C#は*6、Pythonは*4、TypeScriptは*7による)
言語 指定のしかた 何も付けないとき 守られ方
Java public・protected・private・修飾子なし(パッケージプライベート) 同じパッケージ内から見える コンパイラが検査する
C# public・protected internal・protected・internal・private protected・private(型には file も) クラスのメンバーは private、名前空間直下の型は internal コンパイラが検査する
Python 先頭に _ を付ける慣習、__ による名前修飾 どこからでも見える 言語としては強制しない
TypeScript public・protected・private、JavaScriptの #(privateフィールド) public private は型検査のときだけ、# は実行時も

Javaでは、private を付けたメンバーはそのクラスの中からだけ使え、protected を付けたメンバーは同じパッケージの中と、ほかのパッケージにあるサブクラスから使えます。Javaチュートリアルは選び方の指針として、意味のある範囲で最も制限の強いアクセスレベルを使うこと、特に理由が無ければ private にすること、定数を除いて公開フィールドを避けることを挙げ、公開フィールドは特定の実装に縛られてコードを変えにくくなる、と説明しています。*3

C#には6つのアクセス修飾子があり、internal は同じアセンブリ(1回のコンパイルで作られる .dll や .exe)の中からだけ使えるという意味です。InternalsVisibleTo 属性を使えば、指定したほかのアセンブリにだけ internal の型を見せることもできます。*6

Pythonのチュートリアルは、オブジェクトの中からしか使えない「private」なインスタンス変数はPythonには無い、とはっきり書いています。代わりに、先頭に _ を付けた名前はAPIの非公開部分として扱う、という慣習があります。__spam のように先頭に _ を2つ付けると _classname__spam に置き換えられる名前修飾の仕組みもありますが、目的はサブクラスとの名前の衝突を避けることで、主に事故を防ぐための仕組みであり、private とされた変数も読み書きできると明記しています。*4

TypeScriptの private と protected は型検査のときだけ守られ、JavaScriptとして動かすときには普通のプロパティ参照で読めます。一方、JavaScriptの # で始まるprivateフィールドは、コンパイル後も非公開のままです。公式ハンドブックは、悪意のある利用者から値を守る必要があるなら、クロージャ、WeakMap、privateフィールドのように実行時にも強く守られる仕組みを使うよう勧めています。*7 どの言語でも、アクセス修飾子は誤用を防ぐための道具で、機密の保護は別の手段で考えます。

getterとsetterの落とし穴

よく見かけるのが、すべてのフィールドを private にし、検査の無い getter と setter を機械的に付ける書き方です。setter が受け取った値をそのまま代入するだけなら、呼び出し側は前と同じように好きな値を書き込めます。不変条件の面では公開フィールドと変わらず、隠れたのは名前だけです。

もう1つは、getter が内部の変更できるオブジェクトをそのまま返す場合です。次の注文のクラスは、明細の追加を add() に限り、合計金額も同時に更新しています。ところが、lines のプロパティが内部のリストをそのまま返しているため、取り出したリストに要素を足すと、合計金額と明細の合計がずれてしまいます。

# 不変条件:total は明細の金額の合計と一致する
class Order:
    def __init__(self):
        self._lines, self._total = [], 0

    def add(self, price):
        self._lines.append(price)
        self._total += price

    @property
    def lines(self):           # 内部のリストをそのまま返している
        return self._lines

    total = property(lambda self: self._total)

o = Order()
o.add(1200)
o.lines.append(800)        # 取り出したリスト経由で中身が変わる
print(o.total, sum(o.lines))   # 1200 2000

実行すると 1200 2000 と表示されました。setter を定義していないので読み取り専用に見えますが、中身は外から変えられます。直すには、tuple(self._lines) のように写しや変更できない型で返します。作った後に値を変えないオブジェクトにする設計もありますが、ここでは立ち入りません。

設計の面では、getter と setter を並べる前に、呼び出し側に何をさせたいかから操作を決めるほうが、カプセル化の目的に沿います。set_balance() を用意する代わりに deposit() と withdraw() を用意する、という考え方です。

実務での使いどころ

特に向いているのは、不変条件を持つ業務のオブジェクトです。金額、在庫数、期間の開始日と終了日、注文の状態の移り変わりなどは、値の組み合わせに決まりがあります。変更を操作に限っておけば、検査のもれを探す範囲がクラスの中に絞れます。

もう1つはモジュールやライブラリの境界です。外に見せる関数やクラスを小さく保ち、残りを非公開にしておけば、内部を作り直しても利用者のコードは変わりません。差し替えたい部品をどこで受け渡すかは、「依存性注入(DI)とは」で扱っています。変わる部分をクラスの内側に閉じ込める形は、「デザインパターンとは」で紹介している多くのパターンにも見られます。

外部に委託するときの確認点

開発や改修を外部に頼むときは、クラスごとの不変条件が設計書かテストで示されているかを確かめておくと、受け入れの判断がしやすくなります。どれを公開のAPIとし、どれを内部とするかの線引きも、最初に決めておきたい点です。

PythonやTypeScriptのように言語が強制しない場合は、_ の付いた名前や private のメンバーを外から触るコードを、リンターやレビューで止める取り決めがあるかを見ます。テストのためだけに内部を公開していないか、見せる範囲を InternalsVisibleTo のような仕組みで絞っているかも確認点です。公開APIを通したテストが揃っていれば、納品後に内部を作り直しても影響を確かめられます。

まとめ:カプセル化で確かめておきたい3つの点

第一に、カプセル化は内部の状態の変更を決めた操作に限り、不変条件の検査を1か所に集める考え方で、元には変わりそうな設計上の決定を隠すというParnasの情報隠蔽があること。第二に、アクセス修飾子の強さは言語で違い、Pythonは慣習、TypeScriptの private は型検査のときだけ守られ、どれも機密の保護とは別に考えること。第三に、検査の無いsetterや内部のリストをそのまま返すgetterでは、privateにしても不変条件は守れないため、呼び出し側にさせたい操作から公開する範囲を決めることです。

LASSICに相談するメリット

LASSICは、システム開発と保守運用を元請(プライムベンダー)として受託しています。公開範囲は、Javaならmodule-info.java の exports とパッケージプライベート、C#なら internal と InternalsVisibleTo、Pythonなら property と frozen な dataclass、TypeScriptなら # のprivateフィールドで示します。設計では、不変条件をどのクラスに持たせ、どの決定を内側に隠し、どこまでを公開のAPIにするかを、改修の予定と変更の履歴から決めます。検証では、GitHub Actionsで型チェック(mypy・tsc)とテストをプルリクエストごとに回し、不変条件は Hypothesis などのプロパティベーステストで確かめ、非公開の名前を外から触るコードは ArchUnit やリンターで検出します。

よくある質問

カプセル化と情報隠蔽は同じ意味ですか

重なりますが、同じではありません。情報隠蔽は、変わりそうな設計上の決定をモジュールの内側に隠すという分け方の考え方です。カプセル化は、データとそれを扱う操作をひとまとめにし、外からは操作だけを使わせる仕組みを指すことが多く、情報隠蔽を実現する手段の1つと考えると整理しやすくなります。

privateにすればセキュリティ対策になりますか

なりません。Pythonの名前修飾は事故を防ぐための仕組みで、TypeScriptの private も型検査のときだけ守られます。アクセス修飾子は誤った使い方を防ぐためのもので、機密の情報は暗号化や権限の管理など、別の手段で守ります。

getterとsetterは付けないほうがよいですか

付けること自体は問題ありません。気をつけたいのは、検査の無いsetterを機械的に付けることと、内部の変更できるオブジェクトをそのまま返すgetterです。値を変える操作は、業務の意味のあるメソッドとして用意し、その中で条件を検査するほうが不変条件を守りやすくなります。

カプセル化を踏まえた設計・改修のご相談

元請(プライムベンダー)として、クラス設計やモジュール構成の見直しから開発、保守・運用までご提案します。

Remoguとリラシクなら、設計の見直しや改修の開発に加わるITエンジニアも探せます。

Remoguは、リモート前提で全国から即戦力のITプロ人材を調達するサービスです。リラシクは、扱う求人がすべてリモートワークのITエンジニア専門転職エージェントです。どちらもLASSICが運営しています。

無料相談はこちら

出典

  1. *1 参考:Oracle「The Java Tutorials: What Is an Object?」(https://docs.oracle.com/javase/tutorial/java/concepts/object.html)。出典:内部の状態を隠しメソッド経由でやり取りすることをデータのカプセル化と呼ぶ定義、6段変速の自転車の例、情報隠蔽の利点を参照(2026年10月確認)
  2. *2 参考:D. L. Parnas「On the Criteria To Be Used in Decomposing Systems into Modules」(Communications of the ACM, Vol. 15, No. 12, 1972年12月)(https://www.win.tue.nl/~wstomv/edu/2ip30/references/criteria_for_modularization.pdf)。出典:KWICインデックスの2つの分け方、格納形式の変更が及ぶ範囲、各モジュールが設計上の決定を隠すとする記述、循環シフトのモジュールの並び順の反省、結論の提案を参照(2026年10月確認)
  3. *3 参考:Oracle「The Java Tutorials: Controlling Access to Members of a Class」(https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html)。出典:public・protected・private・パッケージプライベートの意味、アクセスレベルの選び方の指針を参照(2026年10月確認)
  4. *4 参考:Python Software Foundation「The Python Tutorial 9. Classes」(https://docs.python.org/3/tutorial/classes.html)。出典:9.6 Private Variables(_ の慣習、名前修飾とその目的)を参照(2026年10月確認)
  5. *5 参考:Python Software Foundation「Built-in Functions: property」(https://docs.python.org/3/library/functions.html#property)。出典:@property による読み取り専用のプロパティの説明を参照(2026年10月確認)
  6. *6 参考:Microsoft Learn「Access Modifiers (C# Programming Guide)」(https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/access-modifiers)。出典:6つのアクセス修飾子と file 修飾子、既定のアクセシビリティ、InternalsVisibleTo 属性を参照(2026年10月確認)
  7. *7 参考:TypeScript「The TypeScript Handbook: Classes」(https://www.typescriptlang.org/docs/handbook/2/classes.html)。出典:Member Visibility(public・protected・private)、private が型検査のときだけ守られること、JavaScriptの # のprivateフィールド、実行時に強く守る仕組みの勧めを参照(2026年10月確認)




View