oop encapsulation - dsuz/csharp GitHub Wiki
今回のテーマ
- カプセル化
- プロパティ
準備
- CSharp2-1-2.unitypackage をダウンロードして Unity のプロジェクトにインポートする
- Project のフォルダ "2-1 OOP - encapsulation" の下にある RPGLike シーンを開いて実行し、動くことを確認しておくこと
解説
今回配布したものの問題点
今回配布した LevelController コンポーネントは、_level を public にしているのでよくない。なぜなら、他のプログラマーによって(あるいは、仕様を忘れてしまった自分によって)外部から値を自由に書き変えることができてしまうためである。レベルを下げることもできてしまう。_playerStats が public であることもよくない。同様に外部から書き変えられてしまうためである。
つまり、以下のようなコードが実行できてしまう。
using UnityEngine;
public class Cheat : MonoBehaviour
{
void Start()
{
var player = GameObject.FindObjectOfType<LevelController>();
player._level = 100;
player._playerStats = new PlayerStats(100, 99999, 99999, 999, 999, 999);
}
}
カプセル化の意図
前に述べたように、自由に書き変えて欲しくない(書き変えてしまうと誤動作を引き起こす場合も含む)フィールドを public にはしない方がよい。同様に参照もして欲しくない(参照する意味がない)フィールドも public にしない方がよい。混乱を引き起こす可能性がある。
このように、外部から操作して欲しくない・外部から参照して欲しくない値を外部から見せなくすることを「カプセル化」という。これはオブジェクト指向の三大要素(カプセル化・継承・多態性)の一つであり、重要な考え方である。
オブジェクト指向および C# の設計意図に沿うと、最終的には「すべてのフィールドは private にすべき」となる。これは大規模あるいは多人数による開発では守るべきルールになることがある。
プロパティ
「すべてのフィールドを private にする」と、外部から値を参照したり、値を代入することができないが、これは public メソッドを使うことで解決できる。C++ や java などではそのようなコーディングを行う。C# にはこのために「プロパティ」という構文がある。これはここで既に登場している。
プロパティの構文については、「自動プロパティ」(※1)や、ラムダ式による記述など様々な書き方がある。これはプロパティを記述するとどうしてもコードが長くなってしまうため、より短く簡潔な記述を目指して、C# のバージョンが上がるごとに新しい記述方法が追加されているからである。短く簡潔な記述はその代わり抽象度が高く理解が難しいので慣れが必要である。
(※1)(参照: 『独習 C#』8.1.2 フィールドのアクセス権限とプロパティ - 自動プロパティ を参照
プロパティという言葉の意味
プロパティとは、元々は「資産」という意味である。C# プログラミングとしては「そのインスタンスが持っている値」という意味になる。例えばファイルのサイズなどもプロパティである。
C# のプロパティ構文を使って get や set の時に処理をさせることができるが、get プロパティとは本質的に「パラメーターを受け取らない(戻り値を返す)メソッド」と同じである。同様に set は「戻り値を返さない、パラメーターを一つだけ受け取るメソッド」と似ている。
public なフィールドは、外部から見ればプロパティと見分けがつかない。また、public readonly という書き方もある。
課題
- 配布したプログラムから public なフィールドを全て private にし、代わりに get プロパティで「外部からは参照のみ可能」になるようにプログラムを修正せよ
備考
C# には以下の命名規則(ガイドライン)がある。
- 変数の最初の文字は小文字であること(キャメルケース)
- プロパティの最初の文字は大文字であること(パスカルケース・アッパーキャメルケース)
- 関数の最初の文字は大文字であること(パスカルケース・アッパーキャメルケース)
- フィールドに _ プレフィックスをつける
public なフィールドを宣言することは、1. か 2. のどちらかに必ず違反してしまう。Unity ではコンポーネントのプロパティを設定するためにフィールドを public にするか、[SerializeField] 属性をつけるが、おそらく当初は [SerializeField] 属性はなかったのではないか。Unity が大規模なゲーム開発に採用されるにつれてフィールドを public にすることが問題となり、[SerializeField] 属性が追加されたのではないかと想像している。
Unity では様々なコンポーネント(クラス)を提供しているが、一部のクラスでは 2. のルールが守られていない。例えば Transform.position プロパティ(Transform クラスの position プロパティ)では守られていない。Unity の基本的な(初期のバージョンからある)クラスにはこのようなものがある。これはおそらく初期バージョンの Unity では C# ではなく javascript 等に合わせて作られていたからだと想像している。javascript ではプロパティ・変数・メソッドの全てをキャメルケースで命名する。そして、初期バージョンの Unity ではプログラミング言語に javascript, C#, Boo の3つの言語を使う事ができた。現在では javascript, Boo は廃止され、C# のみを使うことができる。
参照
- 『独習 C# 新版』
- 8.1 カプセル化
- 8.1.1 アクセス修飾子
- 8.1.2 フィールドのアクセス権限とプロパティ
- 8.1 カプセル化