1. はじめに
アクセス修飾子って、家のフェンスやカギみたいなもんだよね:誰がどの部屋に入れるか、誰がカギ穴から覗くだけかを決めるやつ。C#ではこんな種類があるよ:
| 修飾子 | 誰が見れるか |
|---|---|
|
みんな |
|
今のクラスの中だけ |
|
今のクラスと継承したやつ |
|
プロジェクト全体(アセンブリ) |
|
プロジェクト全体+継承したやつ |
|
同じプロジェクト内の継承だけ |
カプセル化の目的は、クラスの実装の詳細を隠して、誰にも勝手にobj.Field = 99999;みたいなことをさせないようにすることだよ。君がOKしない限りね。
2. アクセスレベルの典型的なミス
publicで全部開けちゃう
一番ありがちなミスは、クラスのフィールドやメソッドを全部publicにしちゃうこと。「もしかしたら使うかも?他の人もどこでもアクセスできた方が便利じゃん!」ってノリ。でも、誰かがオブジェクトに変な値を入れたり、内部の詳細を勝手に使い始めると、コードを変えるのが怖くなるよ。
ミスの例:
public class BankAccount
{
public string Owner;
public decimal Balance;
}
これで誰でもこうできる:
var acc = new BankAccount();
acc.Owner = "";
acc.Balance = -1000000M; // いきなり100万の借金!
何がダメ?
セキュリティも常識も全部無視してる。銀行が木の葉っぱやモノポリーの紙幣しか持ってなくても、「好きなだけマイナスOK」は変だよね。
クラスを「structureなのに構造がない」状態にしちゃう
もう一つよくあるのは、フィールドだけじゃなくて、内部のプロパティやメソッドまでpublicにしちゃうこと。本当は外から触っちゃダメなやつなのに。
public class Vault
{
public string DoorPIN = "1234";
public void OpenDoor()
{
// なんか魔法
}
}
どうなる?こうなる:誰でもPINを知って金庫のドアを開けられる。たとえ「テスト用金庫」でも、1ヶ月後にはこの穴を忘れてて、事故るかも。
正しいやり方:
クラスのインターフェースは最小限で守られてるべき。他の人が使うものだけpublic。自分だけのものはprivate。継承用ならprotected。
3. カプセル化のミス
.NETでは「クラスのデータ(状態)はpublicフィールドにしちゃダメ!」ってのが鉄則。全部隠すか、最低でもプロパティでラップしよう。フィールドに直接アクセスはダサいし、バグの温床だよ。
なぜフィールドはprivateにすべきか
フィールドはオブジェクトの内部状態の核。publicにしちゃうと、いつどんな値が入るかコントロールできなくなる。変な値が入るし、フォーマット変えたら全部壊れる。
代わりにこうしよう:
フィールドはprivateにして、外には必要なプロパティやメソッドだけ出す:
public class BankAccount
{
private decimal balance;
public decimal Balance
{
get { return balance; }
private set
{
if (value < 0)
throw new ArgumentException("残高はマイナスにできないよ");
balance = value;
}
}
public BankAccount(decimal initialBalance)
{
Balance = initialBalance;
}
public void Deposit(decimal amount)
{
if (amount <= 0) throw new ArgumentException("0以下は入金できないよ!");
Balance += amount;
}
}
やっちゃダメ:public setで変えちゃいけないデータを開放
本当はgetだけでいいのに、デフォでpublic { get; set; }って書いちゃう人多い。すると誰でもこうできる:
account.Balance = 99999999; // なんでもアリ?
正しくはこう:
プロパティはクラス内だけでsetしたいなら、setに修飾子をつけよう:
public decimal Balance { get; private set; }
もしくは、値をオブジェクト生成時だけセットしたいなら(C# 14):
public decimal Balance { get; init; }
4. protectedも落とし穴になるとき
継承先にドア全開
protectedは継承先に機能を渡すのに便利だけど、データによっては継承先にも触らせたくないやつもある!例えば、子クラスにクリティカルなフィールドを直接いじらせたくない場合とか。
例:
public class SecureVault
{
protected string secretCode = "1234";
}
これでどんな継承先でもこうできる:
public class HackerVault : SecureVault
{
public void Hack()
{
secretCode = "0000"; // 簡単に秘密コード変更!
}
}
アドバイス:
protectedは本当に継承先に必要なものだけに使おう。他はprivateにして、必要な操作はprotectedメソッドで。
5. internalのミスとアセンブリ混乱
internalは「プロジェクト内なら全部OK」って感じで便利そう。でも、プロジェクトがファイル増えたりライブラリ繋げたりすると、衝突が起きる。よく学生が、クラスを完全に隠すべきなのに、プロジェクト内でpublicにしちゃうミスをする。
例:
internal class Logger
{
internal void Write(string msg) { /* ... */ }
}
これで誰でもプロジェクト内のどこからでもLogger.Writeを呼べる。1年後にDLLを他プロジェクトに繋げたら?publicにしたものは全部「裏側」まで見えちゃう。
ヒント:
技術的なクラスはinternalでいいけど、センシティブな部分はやっぱりprivateにしよう。
6. 実践:バンキングアプリ
バンキングアプリの例で、どんなミスが起きやすいか、どう直すかを実際に見てみよう。
ミス例1:Publicフィールド
public class BankAccount
{
public decimal Balance;
public string Owner;
}
問題:誰でも壊せる。
プロパティで修正
public class BankAccount
{
private decimal _balance;
private string _owner;
public string Owner => _owner; // 読み取り専用
public decimal Balance
{
get => _balance;
private set
{
if (value < 0) throw new ArgumentException("残高はマイナスにできないよ");
_balance = value;
}
}
public BankAccount(string owner, decimal initialBalance)
{
if (string.IsNullOrWhiteSpace(owner))
throw new ArgumentException("オーナー名は必須だよ");
_owner = owner;
Balance = initialBalance;
}
public void Deposit(decimal amount)
{
if (amount <= 0)
throw new ArgumentException("入金額はプラスじゃないとダメ");
Balance += amount;
}
}
これでこうやっても:
var acc = new BankAccount("レナ", 1000m);
acc.Balance = -700m; // エラー!setはprivate。
さらにこうしても:
acc.Owner = "ハッカー"; // コンパイルエラー、setなし
— どっちも無理。コンストラクタかメソッド経由だけ。
ミス:見た目だけのプロパティ
よくあるのが:
public int Age { get; set; }
でも年齢はIncreaseAgeメソッド(例えば1月1日だけ)でしか変えちゃダメなのに、直接いじれるのはNG。
7. ビジュアルで覚えよう:ダメな例と良い例
graph TD
A[Public Fields] -->|どこからでも| D[制御不能な状態]
B[Private Fields + Public Methods] -->|ちゃんと定義| E[制御された状態]
F[Public Setters] -->|誰でも変更| D
G[Private Setters] -->|クラスだけ| E
| やり方 | 状態は守られてる? | バリデーションできる? | 拡張しやすい? |
|---|---|---|---|
| Public fields | ❌ | ❌ | ❌ |
| Properties (get/set public) | ❌ | 一部だけ | できるけど危険 |
| Private fields + setがprivateなプロパティ | ✅ | ✅ | ✅ |
8. カプセル化で気をつけるスタイルのコツ
- 本当にクラス利用者に必要なものだけ外に出そう。
- オブジェクトに入るデータは必ずバリデーションしよう。
- 余計なものは出さない:外から変えちゃダメならsetはprivateに。
- 変なロジックは必ずメソッドで。直接アクセスはやめよう。
- 「テスト用だから」と言ってもpublicフィールドはやめよう。
- publicで読み取りだけでも、フィールドじゃなくてプロパティを使おう。あとで拡張しやすいから。
GO TO FULL VERSION