|
ハノイの塔を組み立てなおす |
||
もう一度クラス設計 |
||
前回までで、とりあえずハノイの塔が遊べるところまで作ってきました。でも、なんか気に入らないのです。なんだろうと、自分自身で考えてみると、結局
ということに尽きるような気がします。たしかにハノイの塔をあらわすオブジェクトの名前は towers にしましたが、やはり List オブジェクトであることには変わらないのです。例えば、ぜんぜん関係のない人がこれを見たときに List オブジェクトが 3 つの Stack オブジェクトから成っており、それぞれが塔に相当しているとわかるでしょうか。難しいと筆者は思うのです。 第 3 者でなくとも、例えば 1 年後にこのプログラムを見たときに自分で分かるだろうかと考えてしまうわけです。 やっぱり、分かりやすいプログラムにするほうがいいですよね。 結局、行きつくところは
ということです。 もう一箇所、気になる部分があります。それは Give up をしたときに例外を発生させるという方法です。例外というのはあくまでも、アプリケーションが異常な状態にあることを知らせるために使うもので、制御の流れに使うものでないと思います。例外に頼らずに、Give up を伝えるようにしたいのです。 今回は、これらの点を考慮してもう一度クラスを見直していこうと思います。
|
ハノイの塔をあらわすには | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
今まではハノイの塔をあらわすときには List オブジェクトを使用し、List オブジェクトの要素として塔に相当する Stack オブジェクトを使用しました。Stack には円盤の大きさを保持させています。 ハノイの塔をクラスであらわすときには、今までの構造を参考にして行うことができます。例えば、次のような方法が考えられます。
この 3 種類の方法はどこまでをクラスとしてあらわすかということが異なってきます。1 はハノイの塔をあらわすクラスだけ。2 はハノイの塔と 1 本の塔をあらわすクラス。3 はハノイの塔、1 本の塔、円盤の 3 種類のクラスを使用する方法です。 今回は練習だと思って、ハノイの塔、塔、円盤の全部をクラスであらわしてみましょう。ハノイの塔は HanoiTowers クラス、塔は Tower クラス、円盤は Pile クラスとしました。この 3 種類のクラス含めてクラス図を書き直してみます。まだ、関数などは決まっていないので、クラス間の関連だけを図 3-18 に示しました。
ハノイの塔は常に 3 本の塔があるので、HanoiTowers は 3 つの Tower オブジェクトを持つようになります。それがHanoiTowers クラスから Tower クラスへの関連の横に書いてある数字の意味です。Pile は Tower にささっていることもあるし、ささっていないこともあるので 0 以上をあらわすアスタリスクが書いてあります。 また、HanoiTowers は常に 3 本の塔を持っているので、HanoiTowers オブジェクトが生成されたときには、3 つの Tower オブジェクトを HanoiTowers オブジェクトが生成します。同様に HanoiTower オブジェクトが消滅するときは、3 つの Tower オブジェクトを消滅させます。この関係は図 3-14 であらわしたコンポジッションになります。HanoiTowers クラスから Tower クラスへの関連が塗りつぶされた菱形なのはこのためです。 Tower クラスと Pile クラスは、Tower クラスが Pile クラスを管理しますが、Tower オブジェクトと Pile オブジェクトのライフサイクルは異なるので、関連は集約になります。 このようなくラス構成にしたとき、HanoiTowers などのクラスはどのような機能を持たせればいいでしょうか。 自分のことは自分で行うという指針で行きましょう。例えば、円盤の移動は Hanoi オブジェクトが行うのではなく、HanoiTowers 自身が行うようにすることです。このように考えると、Hanoi クラスと Player クラスの機能で HanoiTowers クラスに移せそうなものは
があげられます。 塔の表示には円盤の表示や塔の表示を組み合わせて行ったことを思い出してください。これはそのまま、Tower クラスや Pile クラスの機能になるはずです。 そのほかの機能を考えてみましょう。円盤の属性としては大きさが重要になります。これは今まで、Stack オブジェクトに円盤の大きさだけを入れていたことを考えれば合点がいきますね。この属性を取り出すための getter 関数が必要になります。これをまとめると Pile クラスは次のようになります。
Tower クラスはどうなるでしょう。今まで、Tower クラスは Stack クラスで代用していたわけですから、Stack クラスで行っていた push, pop, peek を行う必要があります。そのほかには、Hanoi クラスの中で Stack クラスに対する操作としては、円盤が移動できるかどうかのチェックを行うときに Stack のサイズを調べる、Stack が空かどうかを調べるという 2 つの操作を行っていました。また、塔を表示するときにクーロンの生成を行っています。 今まで Stack クラスで行っていたこれらの機能を実装するためにはどうすればいいでしょうか。簡単なのは Tower クラスの属性として Stack オブジェクトを持たせてしまうことです。例えば Tower クラスの peek 関数がコールされた場合、属性として持っている Stack オブジェクトの peek 関数をコールするようにしてしまえばいいのです。 これらをまとめると
最後に残ったのが HonoiTowers クラスです。すでに示したように Hanoi クラスと Player クラスの機能で HanoiTowers クラスに移せそうなものは
があります。表示には print 関数以外にも private な関数群がありましたので、これも忘れないように HanoiTowers クラスに打ちしましょう。属性として、3 つの Tower オブジェクトがあります。後は円盤の最大サイズを持つようにしましょう。
このように新たにハノイの塔をあらわすクラスを作ったことで、今までの Hanoi クラスと Player クラスの属性、関数が少なくなります。属性や関数まで記述したクラス図を図 3-19 に示しておきますので、今までのクラス構成と見比べてみてください。
図 3-19 には見慣れない記号があります。丸から線が延びていて、それが Tower クラスと Pile クラスにつながっています。丸の上には Cloneable となっています。Cloneable は前節で説明したように、オブジェクトの複製が作れるかどうかを示すインタフェースでしたね。 じゃんけんの時 (図 2-7) は、インタフェースをインプリメントするとき三角と点線を用いていました。インタフェースをきちんとあらわすのであればこれでいいのですが、簡易的にインタフェースをあらわすときに図 3-19 のように丸に線で表します。この丸に線をロリポップといいます。 図 3-19 では Tower クラスと Pile クラスが Cloneable インタフェースをインプリメントしていることを示しています。
|
ハノイの塔を再び組み立てる | ||||||||||||||||
再設計をしたものを実装していきましょう。とはいうものの、実際には Hanoi クラスや Player クラスで実装したものを HanoiTowers クラスなどに移しているだけなのでそれほど手間はかかりませんでした。 完成版を次に示しておきます。
例えば、Player クラスの nextStep 関数は次のように変わっています。変更した部分を赤で示してあります。
今までは nextStep 関数を抜けてから Hanoi オブジェクトが円盤の移動を行っていたのですが、移動のチェックができたらすぐに移動を行うようにしてしまいました。このため、PileMovementInfo クラスを戻り値にしなくてよくなりました。 ということは、Give up の情報を戻り値として使用することが可能になります。そこで、nextStep 関数の戻り値は boolean にし、戻り値が false であれば、Give up をあらわしていることにしました。 その他に、変更した部分といえば Hanoi クラスの makeTowers 関数がなくなってしまいました。これは HanoiTowers クラスのコンストラクタになっています。
これが、次のようになりました。
行っていることは基本的に同じなのですが、Stack オブジェクトに内容を入れる処理は Tower クラスが肩代わりするのでなくなっています。その代わりに、円盤を積むかどうかを示すフラグを Tower クラスのコンストラクタの引数に入れました。 新たに記述した部分では Tower クラスの popPile, peekPile, pushPile 関数です。これらの関数は属性で持っている Stack オブジェクトの pop, peek, push 関数を呼びだしています。これらの関数の戻り値は Object クラスですが、Pile オブジェクト以外を Stack オブジェクトに格納することはないので、Pile クラスにキャストしてあります。
ここで示したように、ソフトウェアの設計と実装は 1 回だけで済むものではありません。何度か設計と実装をくり返し行うことでよりよいソフトウェアを完成させるようにしましょう。
|
●作成したソースファイルとコンパイルを行ったクラスファイルはここでダウンロードできます hanoi3.zip (Feb. 2001) |
|