なぜ、カプセル化の理解が重要なのか、その理由

この記事では、当社の新人エンジニア研修の参考に C# を解説します。

前回は「継承を使ってクラスをグループ化する」について学びました。

今回は継承とともにオブジェクト指向の重要概念であるカプセル化について学びます。

カプセル化はデータ隠蔽とセットなのでデータ隠蔽についてもお話しします。これらもオブジェクト指向特有の概念です。

カプセル化とは、オブジェクトのデータとそれに関する処理を一つの単位(クラス)にまとめ、データを外部から直接操作されないようにする仕組みです。オブジェクト指向はプログラムを部品と考えて、その部品の組み合わせで大きなプログラムを作るのです。物理的なモノの世界では車のエンジンユニットだけを交換する、デスクトップパソコンの電源ボックスだけを取り替えるということが可能です。例えば、車でもパソコンでも関連する部品は近くに配置されていますね。物理的なモノの場合は、距離の問題がありますから、関連の近いものを、近いところに並べるというのは自然です。しかし、情報の世界はそうではありませんから、気をつけないとグチャグチャになりがちです。そこでカプセル化が重要になります。

オブジェクト指向にはデータの整理術の側面があります。

データ隠蔽とは、クラスの内部データに対する直接的なアクセスを制限し、外部からの不正な変更を防ぐ仕組みです。私たちは個々の部品、車やパソコンの中身(やそのデータ)に直接触れることができるでしょうか?もちろん、強引にやれば可能ですが(良い子の皆さんはぜひ試してみてください)保証適用外になりますね。逆に言えば、中身に直接触れさせないことによって部品と更には皆さんを守っていると言うこともできます。このイメージがデータ隠蔽です。

この記事では、カプセル化にデータ隠蔽の意味も含めて解説します。

1. カプセル化の意義

「関連するデータと処理」を1つのクラスにまとめる

既に作ってきたクラスは「フィールド」と「メソッド」を1つにまとめていますが、ポイントは“関連する”データだけをまとめる、ということです。

public class LaptopPc
{
    public int Id;
    public string Name;
    // もしここに会社情報をベタ書きすると…?
    // => カプセル化が不十分
}

もし会社名や住所、電話番号など無関係な情報まで LaptopPc に入れてしまうと、責任の境界が曖昧になり保守しづらくなります。
そこで会社情報は別のクラス Company にまとめ、LaptopPcCompanyhas-a関係(フィールドとして持つ)にすると、自然な設計となります。

クラスの設計の考え方には単一責任の原則というものがあります。

この原則は、

  1. 変更する理由が同じものは一つのクラスに集める
  2. 変更する理由が違うものは別のクラスに分ける
  3. 一つのクラスはそのクラスを利用する人に対してだけ責務を負う

という考え方です。

上記の例では会社の住所や電話番号を取得するにもLaptopPcクラスに問い合わせるということになってしまいます。また、社名が変更になった場合や会社が引っ越した場合にはLaptopPcクラスを修正することになってしまいますね。

2. データ隠蔽の意義

外部からデータを直接触らせない

フィールドやプロパティの公開・非公開が重要です。外部に公開されていないメンバーは勝手に変更できないので、意図しない操作を防ぐことができます。

基本は「private フィールド」「public プロパティ」

たとえば、.NETの内部ライブラリを見ると、多くのクラスで内部データ(private field)を持ち、外部アクセスはpublicのプロパティやメソッドに制限しています。

// イメージ例
public class SomeClass
{
    private int _someValue;

    public int SomeValue
    {
        get { return _someValue; }
        set
        {
            if (value < 0) throw new ArgumentException("...");
            _someValue = value;
        }
    }
}

こうすることで_someValue を直接いじれないため、不正な値が入らないように制御できます。

データ隠蔽を実際に行う例

ここでは、オリジナルなクラスにデータ隠蔽を施してみましょう。

例えば、カジノゲームを作成するとします。プレイヤー(Player)は最初にチップの残高(balance)を1,000持ってゲームスタートです。プレイヤーはこの残高からチップを引き出すことができますが、残高がマイナスになることはできません。

public class Player
{
    private int balance = 1000; // 初期値1000

    // 外部から残高を減らす操作はメソッドで行う
    public void Withdraw(int amount)
    {
        if (balance - amount < 0)
        {
            Console.WriteLine("残高不足です");
        }
        else
        {
            balance -= amount;
            Console.WriteLine($"現在の残高: {balance}");
        }
    }
}

public class Example03
{
    public static void Main()
    {
        Player p = new Player();
        p.Withdraw(200);   // OK => 残高 800
        p.Withdraw(1000);  // 残高不足
    }
}

<実行例>

現在の残高: 800
残高不足です

ここで balancepublicにしていたら、外部から p.balance = 99999999; のように無茶な操作が可能になってしまいます。privateで隠蔽し、メソッド経由で操作することで整合性を守れます。

アクセサ(Getter/Setter)メソッド

C#ではプロパティを通じてフィールドにアクセスします。

public class Calculator
{
    private int _num1;
    private int _num2;

    // プロパティを使ったアクセサ
    public int Num1
    {
        get { return _num1; }
        set { _num1 = value; }
    }

    public int Num2
    {
        get { return _num2; }
        set { _num2 = value; }
    }

    public int Add()
    {
        return _num1 + _num2;
    }
}

public class Example
{
    public static void Main()
    {
        Calculator c = new Calculator();
        c.Num1 = 5;
        c.Num2 = 10;
        Console.WriteLine(c.Add()); // 15
    }
}

こうしておけば、数値を設定(setter)・取得(getter)する際に検証ロジックや変換を入れることも可能です。

「フィールドはprivate」「外部はプロパティかメソッドからアクセス」 という設計が一般的です(自動実装プロパティなどの機能もあります)。

3. クラスやインタフェースのアクセス修飾子

C#におけるアクセス修飾子は、基本的に以下です:

  • public : どのプロジェクト(アセンブリ)からでもアクセス可能
  • internal : 同一プロジェクト(アセンブリ)内からのみアクセス可能
  • (private/protected) : クラス/インタフェース自体には使えません(内部クラスを除く)
新人エンジニア研修
メンバのアクセス修飾子

実験1

上図クラスA~Dを作成して動作を検証しなさい。

実験結果のメモ:

4. カプセル化まとめ

オブジェクト指向の三大要素と呼ばれるのは

  1. 継承 (Inheritance)
  2. ポリモーフィズム (Polymorphism)
  3. カプセル化 (Encapsulation)

でした。
カプセル化によりオブジェクトは堅牢になり、外部からのアクセスを制御して整合性を保てます。部品交換がしやすい設計にもつながります。


<まとめ:隣の人に正しく説明できたらチェックを付けましょう>

□ カプセル化とは、関連する情報(データ)とそれに対する処理(メソッド)を1つにまとめ、クラスとして構築することである。カプセル化により、データと処理が密接に結び付き、設計がシンプルかつ分かりやすくなる。

□ データ隠蔽とは、クラスのメンバー(フィールドやメソッド)の公開範囲を必要最低限に限定することである。アクセス修飾子を使用して、外部からの不要な操作を防ぎ、クラスの内部構造を保護する。

□ 「フィールドの値を更新するときには必ずプロパティやメソッドを介して行い、フィールドには直接アクセスさせないこと」がデータ隠蔽の基本的な実践である。C#では、プロパティ(getとset)を使用してこれを実現する。

□ 他のプログラマに設計者が意図しない操作をさせないことがデータ隠蔽の最大の効果である。データ隠蔽によりクラスの利用方法が制約され、意図しない不正な値の設定や、予期しない動作を防ぐことができる。

□ C#の基本的なクラス設計においては、フィールドはprivateまたはprotectedに設定し、外部からアクセスが必要な場合は、プロパティをpublicにすることが推奨される。

以上、今回は「カプセル化と情報隠蔽で部品の完成度を高める」方法について見てきました。

次回は、「12章. 例外処理で想定外の事態に強いシステムにする」です。

例外処理を使って、途中で落ちてしまわない頑強なプログラムを作っていきましょう。

カプセル化と情報隠蔽で部品の完成度を高める 最後までお読みいただきありがとうございます。