カプセル化とは何だろう

 「カプセル化(encapsulation,エンカプセレ-ション)」とは,クラスのメンバーの中で,クラスを使う人に見せる必要のないものを隠すことです。「ブラックボックス化」だと考えてもよいでしょう。現実世界の物(オブジェクト)でも,カプセル化が行われています。例えば,携帯電話です。携帯電話を使う人にはボタンと液晶画面しか見えませんが,その内部にはさまざまな機能が隠されているはずです。なぜ隠すのでしょう? 隠さずにすべてを見せてしまうと,使う人に混乱を招くだけだからです。

 Javaでは,メンバーを記述する際にprivateというキーワードを添えれば,クラスを使う人からそのメンバーを隠すことができます。隠さないで見せるメンバーには,publicというキーワードを添えます。隠すといっても,コードが目に見えなくなるわけではありません。privateなメンバーを外部から使おうとするとコンパイル・エラーになるのです。そのためクラスを使う人は「privateなメンバーの存在を気にする必要などない。自分は,publicなメンバーだけを見ていればいいのだ」と割り切ってクラスを使うことができます。クラスを作る人は,思いやりを込めてprivateキーワードを添えます。すべてのメンバーがpublicでもプログラムは問題なく動作しますが,思いやりに欠けています。以下のような状況を想像してみてください。クラスを作ったAさんと,クラスを使うB先輩の会話です。

Aさん:先輩に頼まれたクラスが完成しました。

B先輩:ご苦労様。メンバーは,何個になったの?

Aさん:変数が100個で関数が50個です。すべてpublicにしてあります。

B先輩:え~っ! ボクは,それらを全部使わないといけないの?

Aさん:いいえ。先輩に使ってほしいのは,20個の変数と10個の関数だけです。

B先輩:おいおい,それなら残りの80個の変数と40個の関数はprivateにしておいてよ!

 先に,クラスには使われ方が3通りあると説明しました。public(見せる)とprivate(隠す)で区別することは,どのようにクラスを使う場合でも有効です。さらに,クラスを継承して使う人だけに見せるキーワード(Javaではprotected),同じディレクトリ内なら見せるキーワード,および同じファイル内なら見せるキーワードなどを持つOOP言語もあります。使われ方に応じて,隠したり見せたりするわけです。いずれにしても,カプセル化とは,クラスを作る人が思いやりを込めて「見せる/隠す」を適切に指定することです。

リスト2●変数をカプセル化したコードの例
図6●カプセル化により変数に不適切な値が代入されることを防ぐ
図7●多態性の例。同じ「鳴け」というメッセージを与えると…
リスト3●多態性を実現する際には,複数のクラスに同名の関数を定義する
図8●給与計算プログラムのクラスの継承関係
リスト4●スーパークラスの定義
リスト5●三つのサブクラスの定義
リスト6●部署の給与を集計するプログラム(一部)

 カプセル化の定番テクニックとして,privateを指定して変数を隠し,その変数を読み書きするためのpublicな関数のペアを公開するということがよく行われます。クラスというカプセルの中に変数を隠して保護するので,カプセル化という言葉の意味にピッタリでしょう。リスト2[拡大表示]は,本を意味するMyBookクラスの定義です。ページ数を表す変数pageがprivateで隠され,pageの値を読み出す関数getPageと値を書き込む関数setPageがpublicで公開されています。「どのみちクラスを使う人にpageの値を読み書きさせるのだから,pageをpublicで公開しても同じではないか」と思われるかもしれませんが,実は,このカプセル化には大きなメリットがあるのです。

 カプセル化を行わずに,pageをpublicで公開してしまったとしましょう。pageのデータ型はint型なので,約-20億~+20億までの整数を格納できます。MyBookクラスを使う先輩プログラマが,pageの値をユーザーに代入させるアプレットを作ったらどうなるでしょう? ユーザーは,本のページ数としてあり得ないような巨大な値を自由に代入できてしまいます。もしもページ数×1万円で原稿料を計算して銀行口座に自動振込されるようになっていたら――パニックになりますね。

 このような危険を防止するために,変数のカプセル化を行うのです。リスト2のMyBookクラスでは,pageがprivateで隠されているので,ページを代入したい場合はpublicなsetPageメソッドを呼び出さなければなりません。setPageでは,引数に指定されたページ数が0~1000の範囲にあればpageに代入し,そうでない場合は代入しません。if (p >= 0 && p <= 1000){…}という部分です。getPageでは,ノーチェックでpageの値を返しているだけなのですが,pageをprivateで隠してしまったので読みも書きもできないため,setPageとgetPageがペアで必要になるのです(図6[拡大表示])。

 MyBookクラスのpageをpublicにして,それを使う人が0~1000しか代入できないようにチェックするコードを記述することもできます。ただし,それでは使う人に対する思いやりが欠けています。MyBookクラスを作る人がチェックのためのコードを書けば,MyBookクラスを使う人はコードを書かなくて済みます。クラスを使う人にとって,効率的になるわけです。クラスを使う人は,publicなメンバーだけを使います。クラスを作る人がprivateなメンバーをどのように改造しても,クラスを使う人のコードに影響を与えません。カプセル化は,保守性も向上させるわけです。

【ここまでのまとめ】
・カプセル化とは,クラスを使う人に見せる必要のないメンバーを隠すことである。
・変数をカプセル化することで,変数を保護し,プログラムの運用上の危険を防止できる。

多態性とは何だろう

 「多態性(polymorphism,ポリモーフィズム)」とは,同じメッセージを与えたときに,それを受け取るオブジェクトによって異なる振る舞いをすることです。例えば,同じ「鳴け」というメッセージを与えても,ネコは「ニャア」と鳴き,イヌは「ワン」と鳴きます。「あなたの給料いくら?」という同じメッセージを与えても,部長さんは「70万円だよ」と答え,課長さんは「50万円かな」と答えます。このように現実世界には,多態性だなぁと思えることが沢山あります(図7)。

 プログラムで多態性を実現するには,異なるクラスに同じ名前の関数を定義します。この場合,メッセージを与えるにはクラスが持つ関数を呼び出すことになります。例えば,先ほどのイヌとネコをMyDogクラスとMyCatクラスで表し,それぞれに「鳴け」というメッセージを与えるための関数sayHelloを定義するとリスト3のようになります。

 多態性は,プログラミングを効率化するものです。なぜなら,さまざまなクラスで似たような機能の関数が同じ名前で定義されていれば,クラスを使う人は覚えることが少なくて済むからです。もしも,別にネズミを表すMyRatクラスがあったとしたら,「チュウチュウ」と鳴く機能は,sayHelloという名前の関数が持っていると予測できます。

【ここまでのまとめ】
・多態性とは,同じメッセージに対してオブジェクトごとに異なる振る舞いをすることである。
・異なるクラスに,同じ名前の関数を定義しておけば,クラスを使う人は覚えることが少なくて済む。

OOP言語の学習者が目指すべきゴール

 最後に,OOP言語の学習者が目指すべきゴールをお教えしておきましょう。OOP言語の学習は長い道のりですが,これがわかれば合格というテクニックがあります。このテクニックは,OOP言語の多くの解説書で必ずと言っていいほど取り上げられており,情報処理技術者試験のJavaのプログラミング問題の定番テーマにもなっています。それは「スーパークラスの配列で,複数の異なるサブクラスのインスタンスを一元管理する」というテクニックです。

 先ほどのイヌとネコでは実用的ではありませんので,給与計算プログラムを例にしましょう。プログラムのほんの一部だけを作ります。部長さん,課長さん,担当さんをそれぞれ表すBuchoクラス,Kachoクラス,Tantoクラスを定義し,それぞれのクラスのメンバーとして給与の金額を返すgetKyuyoという関数があるとします。複数のクラスに同じ名前の関数があるのですから,それらを汎化して社員を表すShainクラスを定義しましょう。ShainクラスのgetKyuyoを,Buchoクラス,Kachoクラス,Tantoクラスで継承すれば効率的だからです(図8[拡大表示])。

 さて,ここからが面白いところです。スーパークラスであるShainクラスのコードを記述しましょう。給与の値を返すgetKyuyoの処理は,どうしたらよいでしょうか? 役職が決まれば給与の値も決まりますが,Shainクラス(社員)では給与の値を決められません。仕方がないので,仮の値として0を返すようにしておきましょう(リスト4)。

 サブクラスであるBuchoクラス,Kachoクラス,Tantoクラスは,0を返すgetKyuyoを継承することになります。こんな関数を継承しても役に立ちませんね。スーパークラスから継承した関数の機能がサブクラスに合わない場合は,サブクラスで同名のメソッドを記述すれば上書き変更できます。これを「オーバーライド(override)」と呼びます。部長さんの給与は70万円,課長さんの給与は50万円,担当さんの給与は30万円としましょう(リスト5)。

 このプログラムをどう思いますか?「どうせ上書き変更するなら汎化などしなければよかったのではないか」と思われるでしょう。いえいえ,汎化したことには,大いに意味があるのです。部長さん1名,課長さん2名,担当さん3名という部署の社員の給与を集計してみましょう。汎化を行わなかったら「役職ごとに給与を求めて集計する」という手順になるでしょう。汎化を行ったことで「社員の給与を一気に集計する」という手順が実現できるのです。

 リスト6[拡大表示]は,クラスを使う側すなわち給与を集計するプログラムです。スーパークラスであるShainクラスをデータ型とした配列を宣言していますが,配列の個々の要素に代入されているのは3種類のサブクラスのインスタンスです。繰り返しを行うfor文のブロックの中にある

goukei += s[i].getKyuyo();

に注目してください。コンパイラは,s[i].getKyuyo( )の部分を「sのデータ型はShainクラスだな。ShainクラスのメンバーにはgetKyuyoがあるのでOKだ」と解釈してエラーにしません。ただし,s[0]~[5]には,Shainクラスのインスタンスでなく,3種類のサブクラスのインスタンスが代入されていて,それぞれ異なる値を返すgetKyuyoが呼び出されます(これは,多態性の一種だと考えることができます)。まるで,コンパイラをだましているようですが,スーパークラスの配列で,複数の異なるサブクラスのインスタンスを一元管理することが実現できたわけです*1

 このテクニックを面白いと思っていただけたなら,きっとOOPを大好きになれるでしょう。もちろん,これもクラスを使う人への思いやりのために活用するテクニックなのです。ここで再びAさんとB先輩の会話です…。

Aさん:先輩に頼まれた10個のクラスができました。

B先輩:どれどれ。おおっ! スーパークラスが一つあるんだね。これなら,一元管理できて便利だ。ありがとう!!

【ここまでのまとめ】
・スーパークラスから継承した関数の機能を,サブクラスで上書きして変更できる。
・スーパークラスの配列で,異なるサブクラスのオブジェクト一元管理できる。

☆               ☆               ☆

 皆さん,いかがでしたか? OOPの三本柱である継承,カプセル化,多態性の機能と活用方法を理解できたことでしょう。OOPの心構えもわかりましたね。そう,「思いやり!」です。最後に,もう一度だけ繰り返し注意しておきますが,OOPの三本柱は補足的な機能であり,常にすべてを使わなければならないわけではありません。皆さんが作ったクラスを使う人への思いやりを込めて活用すべき機能なのです。思いやりの気持があれば,OOPをマスターでき,OOPを採用したプロジェクトを成功に導くことができます。本当ですよ。

 次回(最終回)は,OOPの考え方を図示する際に使われるUMLの説明をします。どうぞお楽しみに!