...ing logging 4.0

はてなブログに移行しました。D言語の話とかいろいろ。

クラスの外と中

研究室で,クラスの外と中という話をよくするのだけど,なんだかあまりわかってもらえないようなのでここに書く.
コードはC++のものになってしまうが,大体どんな言語でもオブジェクト指向なら一緒(ほんまか?)だと思われるので,大丈夫だろう.
適当に読み替えていただきたい.

さあ,長文を読む覚悟をして,下のリンクをクリックしよう(^^;

クラスの外と中というのは,クラスを利用者側から見るのか,設計者側から見るのかということだ.
もちろん,外がクラスを利用する側,中がクラスを設計する側である.
どうも,オブジェクト指向を覚え始めた人は,クラスには利用者がいるという考えをしていないようだ.
クラスを設計する(自分で作る)ときには,このことを念頭におく必要がある.

これはよく知られていると思うが,クラスというものは,原則として,そのクラスだけで自己完結していなければならない.
自己完結にもいろいろあるが,ここではカプセル化されているということである.
では,情報を公開してくれないカプセル化されているクラスから情報を得るためには,どうすればよいのか.
それには,外から中の変数の値を得ることだけ許し,外から中の変数の値を変更することを禁止すればよい.
たとえば,C++ならこうだ.


class ClassName
{
private:
int _value;
public:
ClassName() { _value = 100; } // コンストラクタ.クラス内部の変数_valueを100に初期化
int getValue() { reutn _value; } // _valueの値100を返す関数
};

void main()
{
ClassName instance;
printf("Value = %d\n", instance.getValue());
}

// (実行結果)
// Value = 100

やっていることはわかるだろうか?
ClassNameのgetValue()は,クラス内部の変数_valueの値を返すだけである.
ここでは,main()がクラスの利用者側,getValue()がクラスの設計者側である,
_valueに対し,クラス内部からは読み書きでき,クラス外部からは読み込みしかできないことがわかるだろうか.
従って,このClassNameクラスは_value変数に関して,利用者側main()に対し,カプセル化されているのである.

今日,誰かが「privateに置いたからカプセル化できている」と言っていたような気がする.
それは確かにそうなのだが,それだけだと外から必要な情報を得ることができなくなってしまう.
上記の例のように,必要な情報だけを関数を介して得られるようにすることもカプセル化なのである.

もちろん,書き込みだけを許可することもできる.
getValue()を削除し,


void setValue(int inValue)
{
_value = inValue;
}

このような書き込み用の関数を追加すればよい.
ちなみに,関数名はあくまで関数名である.
get*****にすれば読み込み用になって,set*****にすれば書き込み用関数になるんだね,なんて言われたら困ってしまう.
とりあえずCから出直そう.

また,読み込み用の関数と,書き込み用の関数を定義すれば,実質,publicにしているようなものである.
この場合はカプセル化しているとは言えないだろう.
ただ,この場合でも,値の読み込み/書き込みのタイミングで処理を追加(ログを取るなど)することが容易になる.
面倒かもしれないが,毎回,数タイピング分の余計な時間がかかるだけなので,今後のための投資を惜しまないようにしよう.
原則として,クラス内部の変数に対するアクセスは,すべてアクセス用の関数を介して行うようにしたい.

これで少しは,クラスの利用者側と設計者側のイメージがつかめただろうか?
では次の問題.
private関数は誰のためのものか?
privateはクラスの外からアクセスすることができないアクセス指定子である.
従って,もちろん,クラス内部で使用するためのものである.
具体的には,このようなクラスがそうである.


class ClassName2
{
private:
int _value;
void x2() { _value = 2 *_value; } // _valueを2倍する
public:
ClassName() { _value = 100; } // コンストラクタ.クラス内部の変数_valueを100に初期化
// 呼ぶたびに_valueの値を2倍して返す関数
int getValue()
{
x2();
reutn _value;
}
};

void main()
{
ClassName2 instance;
printf("Value = %d\n", instance.getValue());
printf("Value = %d\n", instance.getValue());
printf("Value = %d\n", instance.getValue());
}

// (実行結果)
// Value = 200
// Value = 400
// Value = 800

privateに追加したx2()という変な名前の関数は,getValue()の中(つまりクラスの中)でしか使わない関数である.
中でしか使われないからこそpriavteに置かれている.
ClassName2の場合,getValue()の機能が変わっていて,「呼ぶたびに_valueの値を2倍して返す関数」となっている.
getValueの機能がこうだとすると,もしx2()がpublicで,外から呼ぶことが可能になっているとどうなるだろうか.
ClassName2の設計者の知らないところ(もちろんmain()とか)でx2()を呼ぶ人がいたら,
設計者の意図は崩壊し,getValue()が正しく機能しなくなってしまう.

このことを理解すると,普段,1つ(あるいは適切な数よりはるかに少ない数)のクラスのprivate関数たちだけで
済ませてしまっている人は,何か思うところがあるのではないだろうか?
もし1つしかクラスがないのなら,それは全体がグローバルな関数であるのと大差がない.
そうではなく,カプセル化されるべきクラスの単位を見極めて,適切な粒度のクラス毎にプログラム全体を分割してこそ,
オブジェクト指向の利点である再利用のしやすさや,プログラムの見通しのよさを得ることができるのである.

そうでなければ,オブジェクト指向なんて面倒なことこの上ないのである.