おおざっぱオブジェクト指向

C++とかみててもいまいちオブジェクト指向ってわかりにくかったんだけど、Objective-Cを使ってみてようやく意味がわかったよ。その理由をおおざっぱに説明していこうというコーナーです。

1. 手続き型とオブジェクト指向。

よくオブジェクト指向の説明で継承がどうとか隠蔽がどうとかいろいろ説明されてるのを見てきたけど、実際のところあんまり気にしなくていいみたい。というかC++を例にしてる場合は絶対まともなオブジェクト指向の説明にはならないと思うな。というのもC++は厳密に言うとオブジェクト指向じゃないから。いや、指向はしてるんだけどオブジェクト化しきれないというのが正解なのかな。すごいつっこまれそうだけど理由をおおざっぱに説明していきます。

まず、たとえ話でよくほ乳類がどうとか書いてあるのを見かけるけど、あれも気にしなくていい。

じゃあたとえるなら手続き型って何?オブジェクト指向って何?ってことだけど、ゲーム機で説明するね。

手続き型で作られたものってのは、おおざっぱにいうとPSPとかゲームボーイとかの携帯型ゲーム機だ。

いろんな「部品」を組み合わせて1個の完成品を作るイメージだね。ボタン1個とか液晶画面とかパーツ一個一個には分けられるのだけど、全体としては一つという感じかな。小さく無駄なくできるけど、故障したら全バラシ、PSP2や3やスーパーウルトラゲームボーイとかがでたらまるごと買い換えなきゃいけない。拡張するにも専用品がいる。とそういう感じ。

対してオブジェクト指向って言うのは、プレステ本体+モニタ用のテレビ+プレステのコントローラ+テレビのコントローラとかいうふうに、いくつもの「独立した完成品」を組み合わせてゲームをする環境をつくるんだ。この場合はいろいろ融通が利くよね。たとえばゲームプレイを記録するためにビデオデッキを追加したり、もっとでかい画面でやりたかったらシステム中のテレビだけ取り替えたり、いい音が欲しければオーディオシステムだけ取り替えたりできる。もちろんプレステ3が出ても4が出ても取り替えるのはそこだけで他の部分はそのまま使えるよ。

ただ、一見便利に見えるけど、「ゲームをする。」って言う目的に限って言うと無駄が多いし、全部の完成品の値段を考えるとコストも高くついちゃうし、構成要素のうちどこまで細かく完成品として扱うかって言うのも問題になってくる。

両方の一番の違いは「完成品」を組み合わせるか、「部品」を組み合わせるかなんだ。プログラムを書く時、「部品」を組み合わせる感覚で書いちゃうとオブジェクト指向って言うのはとたんに解らなくなる。あ、もちろん動くモノはできるんだけど、「オブジェクト指向でする理由」がわかんなくなっちゃう。訳のわかんない手間だけ増えてやってられねー!となるわけ。

もちろん、厳密にすべてのオブジェクト指向言語がこんなにウマイ仕組みなのかというと、そんなこと無いよ。

なぜならオブジェクト指向っていうのはあくまでも指向してるだけだから。実際にオブジェクト化できるかどうかはまた別の問題なのだ。なんかだまされた気がするけど、これに気づかないとどんどん罠にはまることになる。

2.じゃあオブジェクト化するのに必要なことってなに?

本題に近づいてきたよ。「部品」であっても、「完成品」であっても、組み合わせて動くようにするにはどうにかしてモノとモノをつないだりデータを受け渡したりする必要があるよね。現実の世界で代表的な物としては、ケーブルでつないでデータを送ったり、リモコンでデータを送ったりとか、はたまたビデオカセットやCD-ROMのようにデータを受け渡す専用のオブジェクトをつかったりするよね。

オブジェクト指向言語では、この接続のために「メッセージ送信」って言う機能をもってる。じつは、この機能こそがオブジェクト化するためのキモの機能なんだ。「メッセージ送信っていっても、単なる関数呼び出しのことじゃないの??」って思う人がいるかもしれない。かくゆうわたしも最初はそう思った。そしてはまった。実は、これこそがC++がオブジェクト指向をうたいながら、実際はオブジェクト化しきれない理由なんだ。

ここで、ちょっと自分のPCの状態を見てみて欲しい。たいていのデスクトップPCだと、モニタがあって本体があってキーボードとマウスがあってそれがケーブルでつながってるよね。なんとなくだけどそれぞれ完成品だし、オブジェクト指向してる感じがする。

でも、よくよく考えてみて欲しい。そのモニタは他のマシンで使えるかい?そのキーボードは?たとえばうちには大昔のNeXTCubeっていうマシンのキーボードが転がってるけど、これなんか今文章を打ち込んでるこのマシンにはつながらない。

機能的には使えるはずなんだけど、コネクタの形が特殊で物理的につながらないんだ。

この状況をよくよく考えてみよう。パソコンAでキーボードBを使うためには、パソコンAのコネクタ形状をいじって物理的につなげられるようにするか、キーボードBのコネクタ形状をいじるかして、物理的な接続を確保した後、それに対応したドライバを書かなくちゃならない。そうしないとAもBもどちらも使うことはできないんだ。ああ、ちょっとちがうな。パソコンAはとりあえずキーボードなしでも動かせる。でもBは電源さえももらえないから、全く機能しないんだ。

ちょっとまってくれ!!AもBも完成品で、手を入れる必要はないのじゃなかったのか!?。

これがオブジェクト化しきれない理由の一つ。静的束縛の問題だ。静的束縛によって、相互に依存関係ができてしまうと、独立した完成品として作ってたつもりでも実際は大がかりな部品になってしまう。携帯ゲーム機をつくるために、基盤の上の配線パターンに部品をくっつけていくのと意味合い的には変わらなくなっちゃうんだ。「メッセージ送信が関数呼び出しに思える理由」が、これだ。

もちろん、全く同じって訳じゃないよ。物理的な制限があっても、規格や約束事を決めて、それを守ってる限りはある程度の互換性は確保できる。だからなおさらややこしくなると言う気もするけど、それを知ってれば混乱することも少ないと思うな。

まとめると、「静的束縛な言語は、ケーブルで物理的に物と物をつなぐようなもの」ということになる。利点は確実性、欠点はあなたのPCをケーブルつないだまま移動しようとしたり、PCラックのうらがわに潜ってスパゲティ状のケーブルの束を発見したりした時に感じたことと同じ。そして、オブジェクト個別では機能しにくいこと。だ

じゃあ、オブジェクト指向なメッセージ送信って何?と思うかもしれない。

答えは、あなたがテレビのチャンネルを変える時とかにみつかる。そう、リモコンだ。

リモコンでメッセージを送る時、リモコン自身は相手が誰だってことは全く気にしないよね。これはどんなシステムの中でもリモコンそれ自身の機能は100%働くことを意味するんだ。Objective-Cではこれを「nilに対するメッセージ送信」という機能で実装している。

そして、リモコンからメッセージを受ける側は自分がそれに対応してれば何らかの反応をするし、してなければ何もしない。この、メッセージを受けたけど何もしないという選択肢があるのがポイントなんだ。

同じメーカのビデオデッキが複数台あるときなんか、1個のリモコンで2個同時に電源が入ったりしてどっきりしたことはないかい?こういった現象は(ちょくちょく起きると困るから制御する方法があるけども)ケーブルでつないでるコントローラからは改造なしでは絶対に不可能なんだ。実現するにはケーブル付きのコントローラのケーブルを分岐させたりといろいろ工作が必要になる。

つまり、本当にオブジェクト化(オブジェクトとして独立)するためには、物理的な接続をなくさなくちゃいけない。っていうことだ。

気づいてみれば当たり前のことで拍子抜けだけど、これは事実だ。

ただ、これがいいか悪いかってことじゃないのは覚えておいて欲しい。たとえばリモコンや無線LANで問題になったりするのと同じで、受け取って欲しくないとこまで信号がいっちゃったりすることもあるし、途中に信号を遮る物があった場合原因を探るのに手間がかかったりする。

逆に、目的の場所に確実に信号を送りたい時なんかはケーブル使った方がいいことも多いよね。

だから上手いことケースバイケースで使い分けるのが一番じゃないかな。

それから、上で2個同時に電源が入る話をしてるけど、実際のところはそうはならないことがほとんどだ。ほんとは、完全にオブジェクト化されれば存在だけじゃなくて動作も独立化するのが理想なんだけど、残念ながらたいていの場合そうはなってない。もともとCPUって、基本的にはいっぺんに1個の命令しか実行できない物だし、スレッド分けして擬似的に独立化させることはできるけど、たいていの場合実装の問題でそうはなってないからね。これもオブジェクト指向から指向がとれない理由のひとつで、さらにわかりにくくする原因だ。

おおざっぱにまとめると、

オブジェクト指向はあくまで指向だから、そこんとこ注意。
オブジェクトの独立を実現するのはメッセージ送信にかかってる。
静的束縛しかできない場合は大変だぞ!
PSPもいいけどニンテンドーDSは魅力あふれてるよね。

ということ!(慣れない文体で疲れました。)

3. なぜ「継承」がオブジェクト指向の条件のように語られるのか?

おおざっぱに説明するのが趣旨だけどちょっとおおざっぱ過ぎなので少しフォローするよ。

よく、オブジェクト指向の条件として「継承」があげられることがあるよね。今までの説明だと、オブジェクト化するための条件に「継承」なんてものは全く関係ないことになる。じゃあ、なんで継承が必須のように言われるんだろう?

これも、ケーブルで物理的にしかつなげない仕組みしかもってない場合のことを考えるとその意味がわかるよ。

さっきの例では、大昔のNeXTCubeのキーボードなんて物を持ち出したから上手くつなぐことができなかったけど、これが最近のUSBのキーボードだったら何の問題もなくつなぎ変えることができたはずだ。

つまり、物理的な接続の場合は、それが持ってる規格ってやつが重要になるってことだね。この「規格」を受け継ぐのが、他ならない継承なんだ。USBを受け継いでいればUSBに対応した物が、ADBを受け継いでいればADBに対応した物がそれぞれ使えることが保証されるんだ。基本的に手続き型だと常に1個の規格しか持てないけど、規格を複数持てるようにすれば、その範囲内でならばつなぎ変えもできるようになる。オブジェクトとして完全に独立はできないけども、それっぽく振る舞えるようになるってことなんだ。

これはこれで便利だよ。なにしろ、対応してない物は接続できないんだから、変な物をつないじゃってシステムを壊すようなことは起きにくいってことになる。より実際的というか、物を作ろうとした場合現実的な手段ではあるんだ。

でも、気をつけなくちゃならないのは、これは2番目以降の条件だってことだ。

オブジェクト化の第1条件が「何でもつなげられるようにする」であれば、それができない場合にはじめて「幾つかをつなぎ変えられるようにする」って言う条件が出てくるんだ。これは指向の程度の問題だから、これを持ってオブジェクト指向の必須条件だと思いこんじゃうと痛い目を見るよ。

第2回に続く。