1. はじめに
たとえば、君が最新の車を設計してると想像してみて。中には何百もの複雑なパーツや繊細な電子機器、細かい設定があるよね。エンジン制御ユニットに誰でも勝手にアクセスしてネジをいじれるようにしちゃダメなのは明らか。みんなが好き勝手に触ったら、車が変な動きをし始めて、誰が何を壊したのか調べるために点検しなきゃいけなくなるよ。
プログラミングも同じ。クラスを作るときは、その中身を他人の手から守りたいよね。大事なデータをうっかり壊されたり、外部の人が知るべきじゃないメソッドに手を出されたくない。これがカプセル化ってやつで、オブジェクト指向プログラミングの三大原則のひとつなんだ。
カプセル化を使うと、実装の詳細を隠して、「外から見えるもの」と「中に隠しておくもの」をはっきり分けられる。たとえば、グローブボックスは開けていいけど、ボンネットはしっかりロックしておく感じ。クラス自身が、どのデータを外部に見せて、どれを秘密にするか決められる。これでコードがもっと信頼できて、論理的で、メンテしやすくなるんだ。
もうちょっと厳密に言うと、カプセル化は、データ(フィールド)と、それに関係する動作(メソッド)をひとつの構造体にまとめて、オブジェクトの内部コンポーネントへの直接アクセスを制限するやり方だよ。
2. アクセス修飾子:自分のテリトリーを守る
C#(他のOOP言語もそうだけど)では、カプセル化はアクセス修飾子で実現するよ。これは「ラベル」みたいなもので、クラスのメンバー(フィールド、メソッド、プロパティなど)が外部からアクセスできるか、内部だけかを決めるんだ。
もう何度か出てきたけど、ここでおさらい&ちょっと拡張してアクセス修飾子のリストを見てみよう:
| 修飾子 | アクセスできる範囲... |
|---|---|
|
みんな!プロジェクト内のどんなコードからでも(プロジェクトがライブラリなら外部からも) |
|
このクラスの中だけ |
|
このクラスと、そのすべての子クラスから |
|
今のアセンブリ(プロジェクト)内だけ |
|
今のアセンブリ内、または子クラスから |
|
今のアセンブリ内の子クラスからだけ |
この講義では、よく使うpublic、private、そしてちょっとだけprotectedに絞って話すね。頭がパンクしないように!
内部と外部を分けよう
じゃあ、Dogクラスを書いてみよう:
public class Dog
{
public string Name;
public int Age;
public void Bark()
{
Console.WriteLine($"{Name} はこう言う: ワン!");
}
}
ここでは、すべてのフィールドとメソッドがpublicで宣言されてる。つまり、誰でも犬の名前や年齢を変えられちゃうってこと:
Dog rex = new Dog("レックス", 5);
rex.Name = "シャリク"; // いきなりアイデンティティが変わった!
rex.Age = -999; // 年齢がヤバいことに
でもNameやAgeって、めっちゃ大事なデータだから、誰でも好き勝手にいじらせたくないよね。
こういうのはやめよう
全部のフィールドをpublicにしちゃうと、クラスのロジックが壊れるリスクがある。外部からの操作でオブジェクトが変な状態になるかも。たとえば、犬の年齢をマイナスにしたり、名前を"%$#!??"みたいな変な文字列にしたり。
3. フィールドを隠す:privateを使おう
普通、クラスのフィールドは非公開(private修飾子)にするよ。つまり、値を変えられるのはクラスの中だけで、外からは絶対に無理。
public class Dog
{
private string name;
private int age;
public void Bark()
{
Console.WriteLine($"{name} はこう言う: ワン!");
}
}
これで外部からフィールドに直接アクセスしようとすると:
Dog rex = new Dog("レックス", 5);
rex.name = "シャリク"; // コンパイルエラー
コンパイラが「アクセスできないよ!」って言ってくれる。
なんでこんなに厳しいの?柔軟性は?
「柔軟性」は、特別なメソッドやプロパティ(次の講義で詳しくやるよ)で実現できる。これで、決まったルールでだけデータを変更できるようにするんだ。
4. カプセル化を実践:チェック付きの例
たとえば、犬の年齢をマイナスにできないようにしたいとしよう:
public class Dog
{
private string name;
private int age;
public Dog(string name, int age)
{
this.name = name;
if (age >= 0)
this.age = age;
else
this.age = 0; // 変な年齢は許さない
}
public void Bark()
{
Console.WriteLine($"{name} はこう言う: ワン!");
}
// 年齢を安全に変えるメソッド
public void SetAge(int newAge)
{
if (newAge >= 0)
age = newAge;
// elseで不正な値の時にメッセージを出すこともできる
}
}
これで外部からフィールドを直接壊せなくなった。年齢を変えるには専用メソッドを使うから、ちゃんとチェックできる。
5. フィールド vs アクセスメソッド(getter/setter)
こうやってフィールドをprivateにして、操作用のメソッドを用意するのが、データのカプセル化(data encapsulation)って呼ばれてるやり方。値を読みたいときはgetter、書き換えたいときはsetterを作るのが定番だよ。
public class Dog
{
private string name;
public Dog(string name)
{
this.name = name;
}
public string GetName()
{
return name;
}
public void SetName(string newName)
{
// ここで名前のチェックもできる
name = newName;
}
}
でも! C#にプロパティ(properties)が登場してからは、こういうメソッドはあまり使われなくなった。プロパティを使うと、もっとスッキリ書けるからね(詳しくは次の講義で!)。
6. メソッドやクラスにもアクセス修飾子
フィールドだけじゃない!アクセス修飾子はメソッド(クラスのメンバ関数)や、クラス自体にも使えるよ。
クラスの中だけで使うメソッド(たとえば内部ロジック用のヘルパー関数)は、privateにするのが普通。
publicメソッドは、いわゆるクラスのインターフェース(interfaceキーワードとは別物だよ)、つまりクラス利用者が使う部分ってこと。
7. カプセル化でよくあるミス
ミスその1:なんでもかんでもpublicにしちゃう。
いっぱい公開したほうが便利そうに見えるけど、実はクラスの内部まで外部からいじれるようになって、コードが脆弱で予測不能になる。特に大きなプロジェクトだと危険!
ミスその2:アクセス修飾子を変えて外部コードが壊れる。
リファクタリング中にうっかりメソッドやフィールドの修飾子を変えちゃうと、それを使ってた他のコードが急にコンパイルできなくなったり、動きがおかしくなったりする。特にpublic APIだと致命的。
ミスその3:ローカル変数とクラスのフィールドを混同する。
メソッド内で宣言した変数はそのメソッドの中だけで生きてるってことを忘れがち。クラスのフィールドは全部のメソッドから使える。名前がかぶると特にややこしいバグが出るよ。
ミスその4:privateやprotectedを使わない。
アクセス制限を怖がって、全部公開しちゃう人もいるけど、カプセル化の本質は「余計なものは隠して、本当に必要なものだけ外に出す」ことなんだ。
GO TO FULL VERSION