C#のカプセル化とは?Studentクラスのpublicをprivateに変えて理解しよう

広告

前回までのチュートリアルで、みなさんはすでにこのStudentクラスを書いて、実行して、結果を確認していると思います。

using System;
namespace StudentClassTutorial
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Student student1 = new Student();
            student1.Name = "田中";
            student1.Score = 80;
            Console.WriteLine($"{student1.Name}さんの点数は{student1.Score}点です。");
        }
    }
    class Student
    {
        public string Name;
        public int Score;
    }
}

student1.Name = "田中"; student1.Score = 80; のように、フィールドに直接値を入れるだけの、シンプルなコードです。動かしてみると、ちゃんと「田中さんの点数は80点です。」と表示されたはずです。

このコード、動くには動きます。ですが、実は少し危ういところがあります。今回はこのコードを起点に、「カプセル化」という考え方を学んでいきましょう。

1. このコードの弱点 — publicフィールドの危うさ

NameScorepublicなフィールドです。publicは「クラスの外からも自由にアクセスできる」という意味でしたね。つまり、このクラスを使う側は、Scoreにどんな値でも入れられてしまいます。

student1.Score = -50;

これも、コンパイルエラーにはなりません。実行もできてしまいます。でも、テストの点数がマイナス50点なんて、現実にはあり得ませんよね。同じように、

student1.Score = 100000;

10万点、というのもおかしな話です。Studentクラス自身は、「点数は0点から100点まで」というルールを何も知りません。だから、外から変な値を入れられても、それを止める仕組みがないのです。

publicをprivateに変えると何が起きるかを示すBefore/After比較図

このように、publicなフィールドをそのまま公開していると、「このクラスをどう使ってほしいか」というルールを、クラス自身が守れません。使う側のミス一つで、簡単にデータが壊れてしまいます。

2. 解決策 — フィールドをprivateにして、プロパティで守る

そこで登場するのが「カプセル化」です。考え方はシンプルで、次の2つを組み合わせます。

  • フィールド(_name_score)はprivateにして、クラスの外から直接触れないようにする
  • 代わりにpublicプロパティを用意し、値を出し入れするための「窓口」にする

プロパティのset(値を設定する処理)の中で「その値は正しいか?」をチェックすれば、おかしな値をそもそも受け付けないクラスにできます。図にすると、こんなイメージです。

Scoreに値を代入しようとすると、まず「0~100の範囲か?」という関所を通ります。範囲内ならそのまま保存され、範囲外なら拒否されます。外から見るとstudent1.Score = 80;と書くだけなのは変わりませんが、内部では静かにこのチェックが働いているわけです。

3. 実際に書き換えてみよう

それでは、Studentクラスを書き換えてみましょう。

書き換えたStudentクラスのコード解説図

ポイントは次の4つです。

  1. _name_scoreprivateにして、クラスの外から直接書き換えられないようにしている
  2. Nameプロパティは今のところ単純な受け渡しだが、これも立派な「窓口」であり、呼び出し側のコードは変わらない
  3. Scoresetの中で、0未満または100より大きい値が来たらメッセージを出して処理を止めている
  4. チェックを通過した値だけが、最後に_score = value;で保存される

コード全体は次のようになります。

using System;
namespace StudentClassTutorial
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Student student1 = new Student();
            student1.Name = "田中";
            student1.Score = 80;
            Console.WriteLine($"{student1.Name}さんの点数は{student1.Score}点です。");

            student1.Score = -50;
            Console.WriteLine($"{student1.Name}さんの点数は{student1.Score}点です。");
        }
    }
    class Student
    {
        private string _name;
        private int _score;

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

        public int Score
        {
            get { return _score; }
            set
            {
                if (value < 0 || value > 100)
                {
                    Console.WriteLine("点数は0~100の範囲で入力してください。");
                    return;
                }
                _score = value;
            }
        }
    }
}

注目してほしいのは、Mainメソッド側の書き方が、student1.Score = -50;の行が増えたこと以外はほとんど変わっていない、という点です。student1.Name = "田中";student1.Score = 80;も、書き換え前とまったく同じ書き方のまま使えます。

Scoreに80を代入したときは、これまで通り「田中さんの点数は80点です。」と表示されます。ところが、次に-50を代入しようとすると、コンソールに「点数は0~100の範囲で入力してください。」と表示され、_scoreの値は変わりません。そのため、その後のConsole.WriteLineでも点数は80点のまま表示されます。おかしな値がこっそり紛れ込むことは、もうありません。

4. まとめ:この書き換えで得られる4つのメリット

publicなフィールドをprivate+プロパティに書き換えるだけで、クラスは大きく変わります。

この書き換えで得られる4つのメリットのまとめ図
  • データを守る:不正な点数(マイナスや100点超)を弾ける
  • 誤操作を防ぐ:呼び出し側が知らないうちにデータを壊してしまうことを防げる
  • 変更に強い:後からチェックのルールを変えたくなっても、Studentクラスの中だけを直せばいい
  • 呼び出し側はそのまま:Main()側のコードはほとんど変更しなくていい

おわりに

publicなフィールドを、private+プロパティに変える」— これだけのことですが、クラスに「自分の身は自分で守る」という責任を持たせることができます。これこそが、カプセル化の本質です。

みなさんが最初に書いたStudentクラスも、ぜひ今回のように書き換えて、Scoreに変な値を入れようとしたらどうなるか、実際に試してみてください。「動くコード」から「壊れにくいコード」へと一歩進んだ実感が得られるはずです。

訪問数 11 回, 今日の訪問数 11回

広告

C#

Posted by hidepon