カプセル化

データと振る舞いを一つのユニットにまとめ、内部実装を隠蔽する手法

カプセル化の概念

カプセル化は、オブジェクト指向プログラミングの基本原則の1つで、データ(フィールド)とそのデータを操作するメソッドを一つのユニット(クラス)にまとめ、外部からの不正なアクセスからデータを保護する技術です。

クラス図

図1: カプセル化のクラス図

基本的なカプセル化の例

public class BankAccount {
    // privateフィールド - 外部からの直接アクセスを防ぐ
    private String accountNumber;
    private double balance;
    private String ownerName;
    
    // コンストラクタ
    public BankAccount(String accountNumber, String ownerName) {
        this.accountNumber = accountNumber;
        this.ownerName = ownerName;
        this.balance = 0.0;
    }
    
    // 公開メソッド - 安全なインターフェースを提供
    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            System.out.println(amount + "円が入金されました");
        } else {
            System.out.println("有効な入金額を指定してください");
        }
    }
    
    public void withdraw(double amount) {
        if (amount > 0 && balance >= amount) {
            balance -= amount;
            System.out.println(amount + "円が出金されました");
        } else {
            System.out.println("出金できません:残高不足または無効な金額");
        }
    }
    
    // アクセサメソッド(ゲッター)
    public double getBalance() {
        return balance;
    }
    
    public String getAccountNumber() {
        return accountNumber;
    }
    
    public String getOwnerName() {
        return ownerName;
    }
    
    // セッター(必要に応じて提供)
    public void setOwnerName(String ownerName) {
        this.ownerName = ownerName;
    }
}

シーケンス図

図2: カプセル化の動作シーケンス

アクセス修飾子

Javaのアクセス修飾子を適切に使用することで、カプセル化を実現します:

修飾子 クラス内 同一パッケージ サブクラス その他クラス
private × × ×
デフォルト
(修飾子なし)
× ×
protected ×
public

カプセル化の利点

  • データ保護:外部からの不正なアクセスや改変からデータを守ります
  • 実装の柔軟性:内部実装を変更しても、外部インターフェースが同じであれば利用者に影響を与えません
  • 使いやすさ:複雑な内部ロジックを隠蔽し、シンプルなインターフェースを提供できます
  • コード管理:関連するデータとメソッドをまとめることで、コードの可読性と保守性が向上します

JavaBeansパターン

Javaではカプセル化の実践方法として、JavaBeansパターンが広く使われています:

  • すべてのフィールドをprivateにする
  • 各フィールドにアクセスするためのゲッター・セッターメソッドを提供する
  • 引数のないデフォルトコンストラクタを用意する
  • 必要に応じてSerializableインターフェースを実装する

JavaBeansパターンの例

import java.io.Serializable;

public class Person implements Serializable {
    private String name;
    private int age;
    private String address;
    
    // デフォルトコンストラクタ
    public Person() {
    }
    
    // ゲッターとセッター
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public int getAge() {
        return age;
    }
    
    public void setAge(int age) {
        if (age >= 0) {
            this.age = age;
        }
    }
    
    public String getAddress() {
        return address;
    }
    
    public void setAddress(String address) {
        this.address = address;
    }
}

実践的なポイント

  • フィールドは原則としてprivateにする
  • セッターメソッドでは入力値の検証を行う
  • 変更されるべきでないフィールドにはセッターを提供しない
  • コレクションを返すゲッターは、防御的コピーを考慮する(例:return new ArrayList<>(myList);
  • immutable(不変)クラスを設計する場合は、すべてのフィールドをfinalにし、セッターを提供しない