Gobble up pudding

プログラミングの記事がメインのブログです。

MENU

クラス設計が難しいと改めて思い知る

スポンサードリンク

クラス設計は難しい...

前回のエントリーで書いたテトリス
Windows APIとC++でテトリス - Gobble up pudding
のプログラムを書いて、オブジェクト指向というよりクラス設計は難しい、
本当に難しい!!ということを痛感しました。
絶対にコレ!っていう定石みたいのがほとんどないからなのでしょうか。
一方、フレームワークやライブラリを作っている人のクラス設計は本当に綺麗。
一方僕の書くようなものはともすれば
気を付けてはいるものの
密結合になりがちだったり、
汎用性に欠けたり、変更に弱かったりします。
この差はなんなんでしょうか。センスなんでしょうか?
経験なんでしょうか。
たぶん経験が大きいと思われます。

テトリスで考えてみる...

今回のテトリスの例で考えてみます。
これは私の現在の設計の大まかなやり方
(設計といっても頭で考えながらとりあえず実装してみて
トライアンドエラー状態)なのであまり参考にしないでください。
テトリスで必要なオブジェクトは
テトリスのそれぞれいろんな形をした動くピース、
テトリスの固定されたピースのフィールド
が最低限必要と思い、
PieceクラスとFieldクラスを作りました。
Pieceクラスはいろんな形がありえるけど、
動きや判定はすべて同じなので、
基底クラス(スーパークラス)に全部の実装を書いて、
派生クラス(サブクラス)を作ることにしました。
インターフェースや抽象クラスだと
この場合向かないのかなと判断しました。
実装が全く一緒なので。

とりあえず実装してみるも...

ここまではイイのですが、
PieceとFieldの依存関係に悩みました。
テトリスはPieceとFieldをどちらも考慮して
移動できるかとかの判定をしなくてはなりません。
そこで最初イメージで安直に考えて、
Fieldの中をPieceが動くのだから
FieldクラスがPieceを持てばよいと考えました。
包含関係ですね。
しかし、そうやって実装すると、問題に気づきます。
move()というピースを移動させる関数がPieceに書けないのです。
でもmove(Field *field)などとポインタを渡してやれば
一応は解決できます。もしくはFieldまで渡さなくてもFieldが持っている
blockの情報をわたしてやればOKです。
普通はブロックの情報を渡すと思います。
でも回転させる時もturn(Field *field)
としてやらなければなりません。
それってどうなの?って思いました。
無秩序に書いていくと最悪クラス同士で
お互いがお互いを参照しあっている嫌な感じになります。

PieceがFieldを持つだけになるようにしました。オブジェクトの関係上でもN対1になるし自然ですね。
そうするとスッキリ行きました。
PieceはFieldを参照するだけで、Fieldの値を書き換えません。
Fieldは基本的に自分だけで完結します。
ブロックをフィールドに固定するときに
fixBlock()でPieceのブロック情報を
引数経由でもらうことにはなりましたが、
こればかりはどうしようもない気がしました。

他で考えたことなど...

どのクラスがどんなメンバ関数
(Javaでいうメソッド)を持つべきかということです。
これは考え方があってるのかどうかわからないのですが
クラスが主語で、メンバ関数が述語になるような感じで
クラスを書きました。
PieceがmoveでFieldがmoveはおかしいとか、そんな感じで。
たぶんそんなにこの辺の感覚は
間違ってないんじゃないかと思います。
なお、ついさっきなおしたんですが、
isMovable()をどっちのクラスに置くか悩みました。
moveできるかどうかはPieceだよなフツーってことで、
Pieceに書き直しました。

クラス設計をどう学べばよいか...

GUIの部分はさておき、Tetrisの基本部分は
非常に単純な処理しか行っていないのですが、
クラスを利用して作るとなると
考えなければならないことが結構あります。
非オブジェクト指向言語ではこのような悩みはありません。
ただし、データの責任範囲みたいのが不明瞭になりがちです。
だからC言語でもオブジェクト指向で
作っているケースとかもあるのですが。
この辺のクラス設計って経験を積むしかないんでしょうか?
近道というのはない気がするんですが、
少なくとも比較的小規模のオープンソースを読んだりするのが
いいんでしょうねきっと。
あと、オブジェクト指向が中心の言語のほうが
きっと学習効果がいいんじゃないか
とうっすら思っています。
JavaとかRubyとか。
Rubyはオブジェクト指向強制ではないですが、
どういうわけか使っている人はなぜかスキルが高い気がします。
C++は何が何でもクラスを使うってわけではないことや、
そもそもオブジェクト指向で書こうとしたときにメモリ管理周りで
余計な邪魔が入ります。
直感的に思っているのは
getter/setterが多かったら
それはなんかまずいサインだということです。
あとはデザインパターンとかその辺をみてみたりすると、
いいのかもしれない。
皆さんはどうやってこのあたりの力を付けているのでしょうか?
と書いているうちに良さそうな記事を見つけました。
概念レベルの話でなかなか難しいですが

http://www.primal-inc.com/column/2010/06/01-000083.php
http://www.primal-inc.com/column/2010/07/12-000084.php
http://www.primal-inc.com/column/2010/08/10-000085.php
http://www.primal-inc.com/column/2010/10/06-000086.php

この中でもこのあたりの言葉は聞いたことあるなーと…。
5つのクラス設計に関する原則は本でみた気がします。

単一責任の原則(SRP:The Single Responsibility Principle)
オープン・クローズドの原則(OCP:The Open Closed Principle)
リスコフの置換原則(LSP:The Liskov Substitution Principle)
依存関係逆転の原則(DIP:The Dependency Inversion Principle)
インタフェース分離の原則(ISP:The Interface Segregation Principle)

以上、まとまりのない初級プログラマのたわごとでした!!