|
誰がオブジェクトだ? |
|||
| 登場人物は仮の姿、本当は.... | |||
|
前節で Mastermind に登場する登場人物と、登場人物の動作をまとめました。本節ではこれをもとに Mastermind で使用するオブジェクトを考えていきましょう。 さて、オブジェクト指向のプログラムということなのでオブジェクトを作らなければならないのですが、初心者にとってはどれをオブジェクトにすればいいかということが一番難関になるのではないでしょうか。オブジェクトを抽出する方法論はいろいろ提案されていますが、決定的となる方法論はありません。どれをオブジェクトにするかがマニュアル化されていれば、初心者にも容易にオブジェクトを抽出できるのですが、なかなかそうもいきません。オブジェクトの抽出はアプリケーションを設計する人の経験がものいう、職人技に近いものではないでしょうか。だから、初心者には難関になるのだと思います。 ところでオブジェクトというのはなんなのでしょう。 オブジェクトというのはデータと操作を合わせもったものです。といわれてもなんだかよく分からないですよね。そこで、この講座ではオブジェクトをすべて擬人化してしまい、人間にたとえられるものはすべてオブジェクトとしてしまいましょう。人間には名前などをはじめとするなんらかのデータがあります。また、頼めばいろいろなことをやってくれるということを考えれば、操作を行うこともできます。 たとえば、よく擬人化されるものに車があります。車は車種やエンジンの形式、ガソリンの残量などのいろいろなデータを持っています。また、「移動する」ことや「ライトをつける」などの操作もあります。ということは、車もオブジェクトとして扱うことが可能であるということです。 ところで、ここまでの説明で人間に対応付けられていたものがありましたね。そう、登場人物です。人物というからにはオブジェクトとして扱うことができるはずです。要するに
としてしまうわけです。実際に大規模なアプリケーションを作る場合には、登場人物だけをオブジェクトとするだけではだめで、いろいろな役割をもったオブジェクトが登場します。しかし、初心者にとってはなるべくわかりやすくするということで擬人化できるもをオブジェクトとみなすという定義で十分ではないか思うわけです。 オブジェクトを定義したものがクラスなので、Mastermind では 2 人の登場人物をクラスとして扱うことができます。すなわち
の 2 つのクラスが Matermind で登場するクラスになるわけです。 クラスが決まったので、クラスの定義をしていきましょう。クラスには前述したようにデータと操作から成り立ちます。データは属性とかメンバ変数とかプロパティと呼ばれるものです。一方の操作はメンバ関数などと呼ばれるものがあります。 通常、属性は直接他のオブジェクトからアクセスできないようにします。たとえば、知らない人の名前は分からないですね。名前を知るためにはその人に名前を聞けばいいのです。この名前を聞くことがメッセージセンディングにあたります。オブジェクトがもつ情報が必要なときでも、オブジェクトの内部に直接アクセスするのではなく間接的になりますが、メッセージセンディングを行って情報を得るようにします。 これを情報隠蔽と呼びます。なぜ、こんな回りくどいことをするのでしょうか。いくつか理由があるのですが、ここではその中の一つを示しておきます。 あるオブジェクトが年齢を属性に持っていたとします。この年齢は他のオブジェクトからも直接アクセスできたとします。あるとき、このオブジェクトの作者が誕生日がきたら年齢を自動的に 1 増やすような機能を増やそうと考えましたが、それよりも生年月日を属性に持っておき、今日の年月日から引き算することで年齢を計算したほうがいいのでは思い、実装しました。 ところが他のオブジェクトはそんなことは知らないので、なくなってしまった年齢の属性にアクセスしにいきますが、エラーになってしまいます。結局、年齢の属性にアクセスしていたオブジェクトをすべて変更しなくてはならなくなってしまいました。 メッセージセンディングで情報にアクセスしていた場合、関数の定義さえ変わっていなければ、オブジェクトの中でどのように情報をもつようにしても他のオブジェクトは変更することなく使用できるのです。
|
| 登場人物のやり取りはメッセージセンディング | |||||||
|
図 1-2 で Mastermind のシーケンス図を示しましたが、登場人物がオブジェクトと考えると、登場人物間のやり取りはすべてメッセージセンディングで行われることになります。 オブジェクトということを意識してもう一度シーケンス図を見直してみましょう。今まではメッセージセンディングを行う (関数をコールする) ということは考えずに、人間が行うようにやり取りをあらわしていました。しかし、コンピュータ上の処理の流れということを考えると、単に人間がやり取りを行うように記述すればいいというわけではありません。 問題になるのは、処理の流れが切れていないかということです。たとえば、図 1-3 では、アクターがオブジェクト A の関数 foo をコールすると、foo の中でオブジェクト B の baa という関数をコールします。baa 関数から戻ると、最後に foo 関数の戻り値をアクタに渡しています。関数の戻りを考慮すると、処理の流れは点線で表したように最初から最後まで 1 本の線でつながります。 これとは反して、図 1-4 ではアクタがオブジェクト A の foo 関数を呼び出した後、オブジェクト B がオブジェクト A の baa 関数をコールしています。これだと、処理の流れは 1 本の線でつながりません。 基本的には処理の流れは 1 本の線でつながりますが、マルチスレッドという手法を使用することで複数の線で処理を行うことができます。これについては後の章で説明したいと思います。 処理の流れは 1 本の線で表されますが、普通のシーケンス図だと関数のコールを行っているということを意識しないと、線でつながっていることがわかりにくいと思います。見やすくするためには関数からの戻りを明確にあらわせばいいということもすぐわかります。 実をいうと UML では関数の戻りも定義されています。メッセージの種類を図 1-5 にまとめてみました。a) が今までシーケンス図で使っていたものです。このメッセージがもっとも一般的に使われるものですが、戻りは明示的には示さなくてもいいことになっています。矢印が塗りつぶされた b) は a) と同じように使えるのですが、手続きであることを強調するために使われます。このため、このメッセージは戻りを明示的に示さなくてはいけません。対となる戻りのメッセージは c) の点線矢印で表します。この他にもメッセージの種類はあるのですが、また後で説明したいと思います。
図 1-5 のメッセージを使用して図 1-3 を書き直すと図 1-6 のようになります。これだと処理が 1 本の線になっていることがすぐにわかります。
それではあらためて図 1-2 のシーケンス図を見ていきましょう。この図では問題作成者から処理が始まっています。1 の「正解を作成」は流れに沿っていますが、その次の「回答する」は流れが切れてしまっています。 流れが続くようにするにはどうすればいいでしょうか。線の向きを逆にしてしまえばいいのです。ようするに、回答者が回答するのではなく、問題作成者が回答者に回答を聞きにいけばいいのです。そうすると、図 1-2 は図 1-7 のように描きかえることができます。流れがわかりやすいように戻りの線も描き加えてみました。 これで問題作成者から始まった流れが 1 本の線で結ぶことができました。 本節で出てきたことをまとめると
次節ではこのシーケンス図を基にクラスの定義をしていきましょう。 (Sep. 2000) |
|
|