カプセル化とgetter/setter

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

このレッスンで分かること

  • カプセル化 は内部データを隠してメソッド経由でしかアクセスさせない設計原則
  • ES2022 のクラスフィールド #privateField で本当に private を実現
  • クロージャでも実現可能 ─ function() { let x; return { get: () => x } }
  • getter / setter (get / set キーワード) でプロパティアクセスをカスタム

カプセル化とgetter/setter とは

プライベートフィールドとgetter/setterでデータを保護する方法を学ぼう。本レッスンでは、カプセル化とgetter/setter の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。

カプセル化とgetter/setter

データを外部から直接変更できないようにして、安全に管理する方法を学びましょう。

カプセル化とは

内部のデータを隠し、メソッドを通じてのみアクセスできるようにすることです。

JavaScript

// クロージャによるカプセル化 function createAccount(initial) { let balance = initial; // 外部から直接アクセスできない return { getBalance() { return balance; }, deposit(amount) { balance += amount; } }; }

#プライベートフィールド(クラス)

JavaScript

class BankAccount { #balance; // プライベート constructor(initial) { this.#balance = initial; } get balance() { return this.#balance; } }

この問題では、クロージャで残高を保護する銀行口座オブジェクトを作成しましょう。入出金のバリデーションも実装します。

実務で遭遇するパターン

中級レッスンで学んだことが、実際の業務コードでどう登場するかを整理します。

  • 口座残高の保護 ─ クロージャの balance 変数を外部から直接書き換えられないようにし、deposit / withdraw 経由のみ操作させる
  • バリデーション ─ setter で型 / 範囲チェック
  • キャッシュ#cache を隠しつつ get で参照、invalidate で破棄
  • API クライアント#token を隠して認証付きリクエストだけ提供

コードレビューで指摘されがちなポイント

PR を出すとシニアから入りやすい指摘です。先回りで身につけておけばレビューが一発で通ります。

  • _prefix で疑似 private ─ 古い慣習、本物の # を使う
  • setter で副作用過剰set name(v) { this.fetchData() } のように重い処理を入れると予期しない呼び出しコスト
  • getter なしのフィールド直接公開 ─ 後から変更しづらい、最初から getter で抽象化
  • #field を JSON.stringify で出したい ─ private は出力されない、toJSON() を実装

パフォーマンス考慮事項

  • #field_prefix より速い ─ JIT が完全に最適化、隠蔽もきく
  • getter / setter は若干オーバーヘッド ─ 関数呼び出し分、ホットパスではフィールド直接アクセスのほうが速い
  • クロージャによる隠蔽 ─ メモリオーバーヘッドあり、インスタンスごとに関数が作られる
  • Proxy ─ 全プロパティアクセスをフックできるが、#field より遅い
この章のポイント

ここまでの要点 #field で真の private、getter / setter でアクセス制御。クロージャによる隠蔽も健在だがメモリコスト高、現代は # が主流。

よくある質問

Q. PUT と PATCH はどう使い分けますか?

A. PUT はリソース全体を置き換え、PATCH は部分更新です。例えばユーザー情報全体を送るなら PUT、メールアドレスだけ変えるなら PATCH が REST の慣習に沿います。冪等性は PUT が保証、PATCH は実装次第になります。

Q. 冪等性とは何ですか?

A. 同じリクエストを何度送っても結果が変わらない性質です。GET / PUT / DELETE は冪等、POST は通常冪等ではありません。ネットワークエラーで再送される可能性がある API では、冪等な操作にしておくと安全です。idempotency-key ヘッダで明示する手法もあります。

Q. DELETE のリクエストにボディを入れて良い?

A. HTTP 仕様上は許されていますが、扱わないライブラリやプロキシも多いため避けるのが無難です。複雑な削除条件はクエリパラメータか、POST /resource/delete のようなアクションエンドポイントを使うとトラブルを避けられます。

次のレッスン

次は 継承とsuper で、プライベートフィールドとgetter/setterでデータを保護する方法を学ぼう を学びます。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. カプセル化とgetter/setter の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. カプセル化とgetter/setter とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

入出力例

test-cases.txt

testBankAccount("太郎", 1000, [{"action":"deposit","amount":500},{"action":"withdraw","amount":300},{"action":"getBalance"}])[{"balance":1500,"message":"500円を入金しました","success":true},{"balance":1200,"message":"300円を出金しました","success":true},1200] testBankAccount("花子", 100, [{"action":"withdraw","amount":500},{"action":"deposit","amount":-10},{"action":"getBalance"}])[{"balance":100,"message":"残高不足です","success":false},{"balance":100,"message":"入金額は正の数にしてください","success":false},100] testBankAccount("Bob", 0, [{"action":"deposit","amount":1000},{"action":"withdraw","amount":1000},{"action":"getBalance"}])[{"balance":1000,"message":"1000円を入金しました","success":true},{"balance":0,"message":"1000円を出金しました","success":true},0]
main.js
main.js
学習モード

メモ

カプセル化とgetter/setter

⌘S で保存