はじめに
こんにちは、rinngo です!
この記事では、オブジェクト指向プログラミングの 「カプセル化」 について解説していきます。
また、これは私が所属している徳島大学ゲームクリエイトプロジェクトの勉強会で使用するものです。
興味がありましたら、UnityroomとX公式アカウントなども見に行ってください!
この記事はシリーズとなっており、前回の記事( 【オブジェクト指向】Unityで学ぶオブジェクト指向プログラミング #1 )を先に読んでからこの記事を読むことをおすすめします。
この記事の目標
この記事では以下を理解できることを目標にしています。
- カプセル化を理解できる
- カプセル化を使える
カプセル化について
まずはカプセル化とはなにかを説明していきます。
カプセル化とは?
前回の記事では「フィールド」についての解説をしましたよね。
カプセル化とは、このような「フィールド」や「メソッド」に アクセスできる範囲を絞る という概念のことを言います。
例えば、前回の記事のコードでは PlayerStatus というクラスを作成し、以下のフィールドを実装していました、
_hp_attack
では、これらを Player クラスから直接アクセスできるのでしょうか?
using UnityEngine;
public class Player : MonoBehaviour
{
private PlayerStatus _status;
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
_status = new PlayerStatus();
_status.SetParameters(100, 10);
_status._hp = 200; // _hp を直接書き換える
}
// Update is called once per frame
void Update()
{
if (Input.GetKey(KeyCode.UpArrow))
{
transform.Translate(Vector3.up * Time.deltaTime);
}
if (Input.GetKey(KeyCode.DownArrow))
{
transform.Translate(Vector3.down * Time.deltaTime);
}
if (Input.GetKey(KeyCode.LeftArrow))
{
transform.Translate(Vector3.left * Time.deltaTime);
}
if (Input.GetKey(KeyCode.RightArrow))
{
transform.Translate(Vector3.right * Time.deltaTime);
}
if (Input.GetKey(KeyCode.Z))
{
Debug.Log($"HP: {_status.GetHp()}, Attack: {_status.GetAttack()}");
}
}
}
それでは実行してみましょう。
そうすると、Unity上でエラーが出ましたよね。
Assets/Player.cs(13,17): error CS0122: 'PlayerStatus._hp' is inaccessible due to its protection level
このエラーは、PlayerStatus の _hp はアクセスができないというものです。
ですが、PlayerStatus からは直接値を変更していましたよね?
つまり、 自分のクラス以外からアクセスができないように制限している ということになります。
これが 「カプセル化」 という概念になります。
カプセル化のやり方
ではどうやってカプセル化をしているのかを説明します。
PlayerStatus のフィールドは以下のように実装していますよね。
public class PlayerStatus
{
private int _hp;
private int _attack;
...
}
ここでは private を使用しています。
これは アクセス修飾子 と呼ばれており、これを指定することで どこまでアクセスできるのか を指定できるというものです。
アクセス修飾子は以下のように指定できます。
アクセス修飾子 フィールドやメソッドの定義;
C#では全部で6つあるのですが、今回はよく使う3つを紹介します。
| アクセス修飾子 | アクセスできる範囲 |
|---|---|
| public | どこからでもアクセス可能。 別クラスや別モジュールからもアクセスできる。 |
| private | 同じクラス/構造体からのみ アクセス可能 |
| protected | 同じクラス、派生クラスからのみ アクセス可能 |
派生クラスについてはまだ解説していませんので、今は気にしなくて大丈夫です。
また、これは基本的に 結構色々なものに設定することができます。
以下にできるものを挙げます。
- フィールド
- メソッド
- プロパティ
- クラス
- 構造体
- enum
など...
結構なんでも使えますが、一番使用するのは フィールド と メソッド になると思います。
カプセル化をしてみる
カプセル化をしてみるといっても、すでにカプセル化をしてあるので逆に private を public にしてみましょう。
PlayerStatusクラスを以下のように変更してください。
_hp を public に変更して、フィールド名を hp に変更してください。
public class PlayerStatus
{
public int hp;
private int _attack;
public void SetParameters(int hp, int attack)
{
this.hp = hp;
_attack = attack;
}
public int GetHp()
{
return hp;
}
public int GetAttack()
{
return _attack;
}
}
this は「そのインスタンス自身」を指すものです。
SetParameters の引数名も hp なので、そのまま hp = hp; と書くと引数に代入することになりフィールドが書き換わりません。
this.hp と書くことで、引数ではなく「インスタンスのフィールド hp」を指すことができます。
フィールド名をなぜ変えるの?
慣習的なものなのですが、 private のフィールド名は _ をつけるというルールがあります。
これは、privateかどうかを見分けがつくようにするためです。
よって、public に変更するタイミングで _ を外したというわけです。
慣習的なものなので、絶対に守らないといけないわけではないのですが、特に理由がなければやっておくといいでしょう。
では、 Playerクラスで public にしたフィールドを直接使ってみましょう。
using UnityEngine;
public class Player : MonoBehaviour
{
private PlayerStatus _status;
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
_status = new PlayerStatus();
_status.SetParameters(100, 10);
_status.hp = 200; // 直接代入する
}
// Update is called once per frame
void Update()
{
if (Input.GetKey(KeyCode.UpArrow))
{
transform.Translate(Vector3.up * Time.deltaTime);
}
if (Input.GetKey(KeyCode.DownArrow))
{
transform.Translate(Vector3.down * Time.deltaTime);
}
if (Input.GetKey(KeyCode.LeftArrow))
{
transform.Translate(Vector3.left * Time.deltaTime);
}
if (Input.GetKey(KeyCode.RightArrow))
{
transform.Translate(Vector3.right * Time.deltaTime);
}
if (Input.GetKey(KeyCode.Z))
{
Debug.Log($"HP: {_status.GetHp()}, Attack: {_status.GetAttack()}");
}
}
}
エラーなく実行でき、Zキーを押すとConsoleにこのような文字が表示されると思います!
最初は HP が 100 と表示されていましたが、直接 200 に書き換えているので HP が 200 と表示されるようになりました。
カプセル化のメリット
ここまで読んで、「カプセル化をせずにどこからでもアクセスできるようにすればいいのではないか?」と感じたと思います。
ここでは、カプセル化をする メリット について解説しようと思います。
カプセル化をするメリットは主に2つあると思います。
- 値の書き換えの管理
- クラスの扱いやすさの向上
では1つずつ解説していきますね。
1. 値の書き換えの管理
フィールドを public にするということをしましたが、これはオブジェクト指向プログラミングにおいて やってはいけないこと になります。
なぜなら、どこからでも変更ができるようになることで 「どこから値が書き変わっているのか分からなくなる」 や 「不正な値に書き変わってしまう」 という問題が出てくるからです。
では、Playerクラスを以下のように変更してください、
using UnityEngine;
public class Player : MonoBehaviour
{
private PlayerStatus _status;
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
_status = new PlayerStatus();
_status.SetParameters(100, 10);
_status.Hp = 200;
}
// Update is called once per frame
void Update()
{
if (Input.GetKey(KeyCode.UpArrow))
{
transform.Translate(Vector3.up * Time.deltaTime);
}
if (Input.GetKey(KeyCode.DownArrow))
{
transform.Translate(Vector3.down * Time.deltaTime);
}
if (Input.GetKey(KeyCode.LeftArrow))
{
transform.Translate(Vector3.left * Time.deltaTime);
}
if (Input.GetKey(KeyCode.RightArrow))
{
transform.Translate(Vector3.right * Time.deltaTime);
}
if (Input.GetKey(KeyCode.Z))
{
Debug.Log($"HP: {_status.GetHp()}, Attack: {_status.GetAttack()}");
_status.Hp = -1; // -1 という不正な値を設定する
}
}
}
これで実行してください。
すると、Zキーを2回入力すると、HPの値が通常ではありえない -1 という数字が表示されます。

この挙動はとても不自然ですよね。
これだけの小さなコードならいいですが、大規模なコードを扱うようになるとどこに問題があるのか探すのがとても大変だという問題があります。
ここで、HPの値を private にして外部から簡単にアクセスできないようにし、メソッドを介してアクセスするようにしてみましょう。
HPを設定するメソッド内では 0 未満の値を設定しようとしたときになにもせずに終了するようにしています。
public class PlayerStatus
{
private int _hp;
private int _attack;
public void SetParameters(int hp, int attack)
{
_hp = hp;
_attack = attack;
}
public void SetHp(int hp)
{
if (hp < 0)
{
return; // 0未満のときは無視する
}
_hp = hp;
}
public int GetHp()
{
return _hp;
}
public int GetAttack()
{
return _attack;
}
}
これをPlayerクラスから使用してみましょう。
メソッドを介して -1 を設定しようとしてください。
using UnityEngine;
public class Player : MonoBehaviour
{
private PlayerStatus _status;
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
_status = new PlayerStatus();
_status.SetParameters(100, 10);
}
// Update is called once per frame
void Update()
{
if (Input.GetKey(KeyCode.UpArrow))
{
transform.Translate(Vector3.up * Time.deltaTime);
}
if (Input.GetKey(KeyCode.DownArrow))
{
transform.Translate(Vector3.down * Time.deltaTime);
}
if (Input.GetKey(KeyCode.LeftArrow))
{
transform.Translate(Vector3.left * Time.deltaTime);
}
if (Input.GetKey(KeyCode.RightArrow))
{
transform.Translate(Vector3.right * Time.deltaTime);
}
if (Input.GetKey(KeyCode.Z))
{
Debug.Log($"HP: {_status.GetHp()}, Attack: {_status.GetAttack()}");
_status.SetHp(-1); // -1を設定しようとする
}
}
}
実行してZキーを押してください。
すると、以下のようにHPが-1に設定されないようになったと思います。
このように、不正な操作をさせないようにすることができます。
アクセス修飾子に迷ったら、とりあえず以下のようにすれば最低限は大丈夫です。
| 指定するもの | アクセス修飾子 |
|---|---|
| メソッド | public |
| フィールド | private |
また、フィールドを public をするのは、原則禁止 と考えた方がいいです!
2. クラスの扱いやすさの向上
private にすることで、使う側が知らなくていい情報を隠せる というメリットもあります。
例えば、PlayerStatus クラスの内部に計算ロジックがたくさん増えたとします。
public class PlayerStatus
{
public int Hp;
public int Attack;
public int MaxHp;
public int Level;
public void CalcDamage(int attack) { ... } // ダメージ計算(内部用)
public void LevelUpCalc() { ... } // レベルアップ計算(内部用)
public void ValidateHp(int hp) { ... } // HP検証(内部用)
public void SetHp(int hp) { ... } // 外から使う
public int GetHp() { ... } // 外から使う
}
全て public だと、 Player クラスから使う側は「どれを使えばいいのか?」「CalcDamage() も呼ぶ必要があるのか?」と迷ってしまいます。
public class PlayerStatus
{
private int _hp;
private int _attack;
private int _maxHp;
private int _level;
private void CalcDamage(int attack) { ... } // 内部でのみ使う
private void LevelUpCalc() { ... } // 内部でのみ使う
private void ValidateHp(int hp) { ... } // 内部でのみ使う
public void SetHp(int hp) { ... } // 外から使う
public int GetHp() { ... } // 外から使う
}
private で隠しておくことで、使う側は public なものだけ知っていれば良い という状態になります。
「使う側が覚えることを減らせる」 のがこのメリットです。
これは実際に使わないとあまりイメージができないかもしれないので、もし理解できなくてもOKです!
まとめ
この記事ではカプセル化について解説しました。
今回やったことをまとめると以下のようになります。
- カプセル化 とは、フィールドやメソッドにアクセスできる範囲を絞ること
- フィールドは基本
private、メソッドは基本publicにする - カプセル化をすることで 不正な値の書き換えを防ぎ、クラスを使いやすく できる
少し難しい概念だったと思いますが、これを使いこなせるとより扱いやすいクラスの実装をしやすくなるので、ぜひ頑張って使ってみてください!

