カプセル化を理解するための前提知識
各用語の意味を再確認
そして命名規則についても再確認
| 言語 | メソッドの記名方式 |
|---|---|
| java | キャメルケース |
| python | スネークケース |
javaにおける慣例の命名規則ほか
| 対象 | 表記 |
|---|---|
| 変数 | 小文字 |
| 定数 | 大文字 |
| クラス名 | 頭文字は大文字 |
| メソッド名 | 頭文字は小文字のキャメル方式 |
javaではソースファイル名とmainメソッドを持つクラス名は同じしないといけない厳格なルールがある。さらにmainメソッドをもつクラスはpublicをつける必要がある。
ここまでを前提知識としてここからが本題のカプセル化についてのまとめ
カプセル化とは
大切な情報(フィールド)や操作(メソッド)にアクセス制御をかけることにより、悪意や間違いによるメンバの利用を防止し、想定しない利用が発生したときには、その原因箇所を特定しやすくするためのしくみ
メリット
- read only, write onlyのフィールドを実現できる
- クラスの内部設計を自由に変更できる
- フィールドへのアクセスを検査できる → 最適化(リファクタリング)に繋がる
アクセス制御
「何に・何を・どこまで制限するか」ということ。
原則として、データ(フィールド)をクラス外から自由に書き換えられるべきではなく、クラスが提供するメソッドを通じて安全に変更・取得されるべきであるという考え方。
そのため、設計の基本は
- フィールドは原則 private (データを隠蔽し、不正な書き換えを防ぐ)
- 操作窓口となるメソッドは public (外部との安全な取引メッセージ)
- 内部処理用のメソッドは private (外に見せる必要のない仕組みは隠す)
実際のアクセス制御を行ううえでは、下記のアクセス修飾子を使って制限のレベルを選択する。
| 制限のレベル | 名称 | アクセス修飾子 | アクセスを許可する範囲 |
|---|---|---|---|
| 制限が厳しい | private | private | 自分自身のクラスのみ |
| ↑ | package private | なし | 自分と同じパッケージに属するクラス |
| ↓ | protected | protected | 自分と同じパッケージに属するか、自分を継承した子クラス |
| 制限が緩い | public | public | すべてのクラス |
注: 修飾子を何も書かない状態を「パッケージプライベート(またはデフォルト)」と呼び、同じパッケージに属するクラスからのみアクセスを許可する設定ということ
実例
まずはこれまで述べたカプセル化の原則を行わずに作成されたクラスで発生する問題を以下のコード例として再確認。
// 銀行口座を表すクラス(カプセル化されていない)
class BankAccount {
// フィールドがpublicなので、どこからでも自由に書き換えられてしまう
public String ownerName;
public int balance;
public BankAccount(String ownerName, int balance) {
this.ownerName = ownerName;
this.balance = balance;
}
}
public class Main {
public static void main(String[] args) {
// 山田さんの口座を1万円で開設
BankAccount account = new BankAccount("山田太郎", 10000);
// 問題点①:チェックをすり抜けて「不正な値(マイナス)」にできてしまう
account.balance = -500000;
// 問題点②:クラスのルール(秩序)を無視して自由に書き換えられてしまう
account.ownerName = "謎のハッカー";
System.out.println("口座名義: " + account.ownerName);
System.out.println("残高: " + account.balance + "円");
// 出力結果:口座名義: 謎のハッカー、残高: -500000円 (大問題!)
}
}
以上のように問題点が2点確認できるが、そもそもこのような問題が起きないようにクラスを設計することが重要。以下のコードはこれらの問題が発生しないようにクラスを設計し直した例である。
// 銀行口座を表すクラス(正しくカプセル化されている)
class BankAccount {
// フィールドをprivateにして、外部から直接書き換えられないように隠蔽する
private String ownerName;
private int balance;
public BankAccount(String ownerName, int balance) {
this.ownerName = ownerName;
// 初期化時もマイナス残高にならないよう、安全のためにメソッド経由でセットする
setBalance(balance);
}
// 名義を取得するメソッド(Getter)
public String getOwnerName() {
return ownerName;
}
// 残高を取得するメソッド(Getter)
public int getBalance() {
return balance;
}
// 残高を変更するメソッド(Setter)
public void setBalance(int balance) {
// クラス自身のルール:残高がマイナスになる変更は拒否する
if (balance < 0) {
System.out.println("【エラー】残高をマイナスにすることはできません。処理を拒否しました。");
return; // 変更せずに処理を終了する
}
this.balance = balance;
}
}
public class Main {
public static void main(String[] args) {
// 山田さんの口座を1万円で開設
BankAccount account = new BankAccount("山田太郎", 10000);
// 改善ポイント①:直接書き換えようとするとコンパイルエラーになる
// account.balance = -500000; // コメントを外すとエラー:balanceはprivateです
// 改善ポイント②:メソッドを経由させることで、不正な操作をブロックできる
account.setBalance(-500000); // 画面にエラーメッセージが表示され、残高は守られる
// 改善ポイント③:名義を変更するメソッド(Setter)を用意しなければ、外から書き換えられない
// account.ownerName = "謎のハッカー"; // エラー:ownerNameはprivateです
System.out.println("口座名義: " + account.getOwnerName());
System.out.println("残高: " + account.getBalance() + "円");
// 出力結果:口座名義: 山田太郎、残高: 10000円 (安全に守られた!)
}
}