C#でクラスを作ろう(3)/アクセス修飾子

3月 17, 2020

このシリーズについて

C言語やC++言語などを学んではいるけどクラスをあまり作ったことが無い、という方を対象にしています。
このシリーズでは、C#でクラスを作るための基本的な構文を解説しています。C++やJavaなどと共通している概念も多いですが、サンプルコードは基本的にC#で解説します。ところどころ、C++特有の概念を解説することもあります。

クラスを構成するフィールドやメソッドやプロパティなどの各種情報には、非公開(private)なものと、公開(public)されるものがあります。公開すべきかどうかを制御するのが、アクセス修飾子と呼ばれるキーワードです。

スポンサーリンク

フィールド(メンバ変数)の読み書き

クラスというのはプリミティブなデータの集まりをひとまとまりにしたものです。

class Player
{
    public string _name;
    public int _winCount;
    public int _loseCount;
}

この中の、個々の_nameや_winCountなどをフィールド(またはメンバ変数)と呼びます。フィールドに対する読み書きは、

Player player = new Player();
player._name = "倉酢面馬"; //書き込み
string s = player._name;   //読み取り

のようにして行います。

アクセス修飾子publicとprivate

各フィールドは、アクセス可能な範囲をpublicprivateというキーワードで指定します。publicなフィールドにはクラスの内部からも外部からもアクセスできます。privateなフィールドにはクラスの内部からしかアクセスできません。

class Player
{
    public string _name;
    private int _winCount;
    private int _loseCount;
 
    public void ShowInfo()
    {
        Console.WriteLine($"名前:{_name}");     //クラスの内部からは必ずアクセスできる
        Console.WriteLine($"勝数:{_winCount}"); //クラスの内部からは必ずアクセスできる
    }
}
 
//クラス外の別の場所
Player player = new Player();
player._name = "倉酢面罵";   //publicなので外部からアクセスできる
player._winCount = 5;        //エラー。privateなので外部からアクセスできない

アクセス可能な範囲を指定するpublicやprivateのキーワードは、フィールドだけでなくメソッドやプロパティなどにも指定することができます。

情報隠蔽

クラスというのはできる限り、日本語として意味のある「モノ」であることが望ましいです。つまり、意味のあるメソッドによってのみ操作可能とし、意味のあるプロパティによってのみ内部情報へのアクセスを可能とすることが望ましいとされています。

できるだけフィールドをprivateにし、必要な手続きのみをpublicにして、クラスをこのような「望ましい形」にすることを情報隠蔽と呼びます。

適切に情報隠蔽されたクラスは、以下のような3つのメリットがあります。

内部実装を知らなくていい

例えば誕生日を表すクラスがあったとき、

//DateTime型のフィールドで保持
class Birthday1
{
    private DateTime _date;
}
//年月日で保持
class Birthday2
{
    private int _year;
    private int _month;
    private int _day;
}
//西暦1年1月1日からの経過日で保持
class Birthday3
{
    private int _daysFromAD;
}

など、いろいろな内部実装方法が考えられます。

このクラスを扱う側(=外側)の人が、誕生日の日付の表示を取得したいとしましょう。日付の表示は、publicなメソッド"DisplayString"で取得できるものとします。

//オブジェクトbdは、Birthday1型、Birthday2型、Birthday3型のいずれか
Console.WriteLine($"誕生日は{bd.DisplayString()}");
//出力結果:誕生日は1996年3月18日

外側の人の関心事は「日付の表示を取得したい」ということだけであり、クラスの内部には関心がありません。

DisplayStringメソッドの中身は、Birthday1型ならreturn _date.ToString(“yyyy年MM月dd日")、Birthday2型ならreturn _year + “年" + _month + “月" + _day + “日"のようになり、Birthday3型ならとてもここには書ききれない複雑な計算になりますが理論上は一応可能です。

でも、外側の人はそんなことに興味は無いのです。内部がどうなっていようと、DisplayStringメソッドによって日付の表示が取得できるということだけが、外側の人にとっての関心事です。

このように内部実装を知らなくていいという状況は、クラスの内側と外側で別々の人がコーディングを担当している場合にとても便利です。1人で内側も外側も作ってる場合でも、内側を作るときは内側だけ、外側を作るときは外側だけのことを考えればいいので、脳の消費リソースを少なくできます。

オブジェクトが不正な状態になることを防ぐ

class Birthday2
{
    private int _year;
    private int _month;
    private int _day;
 
    public void SetBirthday(Datetime dt)
    {
        _year = dt.Year;
        _month = dt.Month;
        _day = dt.Day;
    }
}

Birthday2型のクラスに、誕生日の日付をセットするSetBirthdayメソッドを定義してみました。このメソッドにより、内部のフィールド_year、_month、_dayに値がセットされます。

もし、_year、_month、_dayがprivateではなくpublicだったら、例えば外部からbd._month = 13という代入ができてしまいます。13月というのは存在しないので、_monthが13であるオブジェクトは不正な値を保持した不正なオブジェクトです。オブジェクトが不正な状態になっていると、その先でどんな予測不能なバグを誘発してしまうかわかりません。

でも、「値をセットするには必ずSetBirthdayメソッドを使う」ということにしておけば、オブジェクトが不正な状態になることを防ぐことができます。

もちろん、クラスの内部を作る人はちゃんとSetBirthdayメソッドをバグが無いように作らなくてはいけませんが、クラスの外側からオブジェクトを扱う人は「この値を入れてしまうとバグが起きるかもしれない」という余計な思考から解放されます。余計なことを考えなくて済むので、脳の消費リソースを少なくすることができます。

フィールドの値が変更される可能性のある箇所を限定的にする

規模の大きいプログラムになると、クラスオブジェクトへのアクセスは頻繁に発生し、クラス内部の値は常々変化します。例えば、Birthday2型のオブジェクトの中身が"1996年3月18日"だったはずなのに、いつの間にか"96年3月18日"に変化していたとしましょう。

「いつの間にか変化した」とは言っても、プログラムを書いたのは人間なので、どこかにプログラミングミスがあったはずです。

もし_yearがpublicだったとすると、規模の大きいプログラムでは_yearをいろんな場面で読み書きしているので、その全ての箇所をチェックしてミスが無いか確認しなくてはいけません。

しかし、_yearをprivateにして、_yearが変化するのはSetBirthdayメソッドを使った場合のみであることが保証されていれば、プログラムの規模が大きくてもSetBirthdayメソッドを使っている場所の周辺だけをチェックすれば、ミスをすばやく見つけることができます。

クラスの内側と外側を意識する

たとえ1人でプログラミングを行っているとしても、クラスの内側と外側で別々の人間がプログラミングをしてるかのように、1人2役を意識してみましょう。

内側の人は「privateなフィールドは私(内部)が責任を持ち、publicなものだけを外に公開する」と考え、外側の人は「内部のことは気にせずに、publicなものだけを扱う」というふうに考えます。

そのあたりの切り分けは、最初は感覚を掴みにくいかもしれません。ぶっちゃけ、全部publicにしてしまったほうがラクといえばラクです。でもまぁ、とりあえずいろいろやってみて、うまくいかなさそうだったら自由にprivateからpublicに切り替えたりして、コツを掴んでいきましょう。

スポンサーリンク