クラスはトップダウンよりボトムアップがいいと思う
今回はゲーム製作の見えざる大切な核心「クラスの作り方」お話です。文章オンリーですが、ゲーム製作の可否を決めるほど重要な内容かなと思いますので、本気でゲームを作りたい方は一読を強くお勧めします!
オブジェクト指向においてクラスはその基本単位です。クラスを組み合わせる事によって様々な仕事をさせ、似たような仕事はクラスを派生させて楽をする。オブジェクト指向が世の中に出てきてもう随分と立ち、javaやその他のオブジェクト指向型言語の登場もあいまって、ようやくその手法がプログラミングの中にしっかり浸透してきたように思えます。
ところで、皆さんはクラスをどのように作成されているでしょうか。「え?まずclassって書いて・・・」という話ではなくて、どうやってクラス構成を作っているかと言うことです。この章は、どのようにしてクラスを設計していくのかという、オブジェクト指向の根幹を成す部分について考えてみます。
@ 生き物は人や犬や鳥になるけど・・・
クラスと言うとよく引き合いに出されるのが「生き物」クラス。「世の中には生き物がいる」と抽象的に考えてから、このクラスを人とか犬とか鳥クラスに派生させて「人は話す」「犬は吼える」「鳥は鳴く+飛ぶ」みたいな継承や拡張を行わせるのが良く出てきますね。他にも「乗り物」クラスを車やバスや飛行機に派生させるというのもあります。
これらは、オブジェクト指向で使われるクラスの仕組みをイメージするのには大変良い例だと思います。ただ、この図式でクラスを覚えると、クラスの設計も自然と「抽象」→「具象」という方向になってきます。実例では、例えばお店の在庫管理などをオブジェクト指向で作るときに、「商品クラスという抽象クラスがあるわけで、A店は商品クラスを派生させて衣類クラスにして、B店は生鮮食品クラスにして云々・・・」と考えてから、「商品には値段と在庫数と、え〜とえ〜と共通点はあと何かな?」などとクラス設計図を仕様に照らして作っていくわけです。
抽象クラスを先に考えてから、それを派生クラスで具体化していくというクラス本来の方向性でクラス設計をするのを、ここではトップダウン方式(幹を決めてから枝を広げていく方法)と呼ぶ事にしましょう。
トップダウン方式は悪い事ではまったくありませんし、現場では良く使用されています。ただ、一般にこの方法でクラスを作るのはしんどいのです。実は、トップダウン方式は「仕様が固まっていて変更が少ない」時には十分に機能します。目の前に成すべきすべての事があるので、抽象概念を決めやすいからです。しかし、変更や追加が多い状態だと、抽象概念に含まれないオブジェクトがポンポン出現しだして、いつまでたっても抽象クラスを決められず、トップダウンの図式を設計できないのです。よって、「よし!クラスを実装するぞ!」っとなかなかスタートを切ることが出来なくなります。
クラス化について、生物を人や犬に派生させる図式を描く本はとても多いのですが、実際はそう簡単に境界線は描けないものなです。
A 人や犬や鳥がいるから「生物」の存在が浮き出てくる
そこで、次のように考えます。
「世の中には人がいる。犬もいる。鳥も確かにいる。人は言葉を話すからSpeak関数を作ろう。犬は吼えるからHowl関数を作ろう。鳥は囀るからTwitter関数を作ろう。」
具象物として世の中に存在するものをとりあえずクラス化するわけです。そしてある程度機能をつけます。ただ、少し作った段階でいったんクラス化の手を休め、じっとそれらを眺めます。
「う〜ん、でも人も犬も鳥も口から音を出してコミュニケーションしているなぁ。それならば先ほどの関数をすべてSpeaking関数としてまとめられるな。ならば上位クラス『声を出す生物クラス』を作ろう」
このように、必要と思われるクラスを作成し、その共通項を見つけては上位クラスに追加していく。そういうクラスの作り方をボトムアップ方式(枝を集めて幹にする方法)と呼ぶ事にします。この方式は言ってみればアドホック(その場限りの)なクラス作成なので非常に無計画に感じられますが、私はトップダウン方式よりもこちらの方式の方がはるかに再利用可能なクラスを作成しやすいと思っています。
B 本当に必要なクラスは目の前にあるじゃないか
トップダウン方式によるクラス設計は、仕様を分析して脳みそで共通項をくくりだして行く作業です。これはウォーターフォールモデルには非常に良くマッチします。しかし、プロのプログラマーでもゲームクリエーターでもない私たちがゲームを製作しようと考えるときに、完璧な仕様を作り上げるのは困難、多くは不可能でしょう。つまり、作り方がそもそもウォーターフォールではないわけです。では私たちの作り方は何なのか?それは多分「スパイラルモデル」に近いものでしょう。スパイラルモデルはプロトタイプを作ることで設計を確かめて、徐々にそれをバージョンアップさせていく設計手法です。こういう設計手法の場合は、最初から仕様がきっちり決まっているわけではないので、「共通項」が何なのかもはっきりしません。だからと言って仕様が完璧に固まるまで待てるはずもないので、目の前にある「確かに必要なもの」からクラスを作っていくわけです。この考え方は「YAGNIの原則」につながるものがあります。「You Aren't Gonna Need It !(そんなの必要ないって!)」とは、間違いなく必要なものだけを作っていこうというプログラム設計の考え方です。そしてこの原則でクラスを作成していくと、自ずと「リファクタリング」したくなってきます。リファクタリングとは作成したプログラムの機能を原則変えることなく効率的で綺麗な状態に改正していく作業の事です。クラス設計で言えば、作成したクラスの共通項を見つけ出して、それを上位にシフトアップしていく作業となります。こう考えると、仕様を完璧に決められないことが前提である時のクラス設計は自然とボトムアップ方式になっていくことがわかります。
ボトムアップ方式で作成された上位クラスは、本当に必要な機能のみが凝縮されています。変なメンバ変数やメンバ関数が入っていませんから、流用がきくようになります。はたと気がつけば、オブジェクト指向における「再利用性」が高いクラスの作成になっていたりするんです。
C 思い立ったらクラスの作成
ボトムアップ方式のさらに良い点。それは「思いついたらクラス化ができる」というところです。「キャラクタを自動で歩かせなくちゃいけない」と思ったとき、トップダウン方式だと「スタートポジション(x,y)にいるキャラクタは節を線形に結びながら速度vで歩いて最後の節に来たときに云々・・・」と仕様を固めてからでないと怖くてクラス設計ができないのですが、ボトムアップ方式だと「少なくともキャラクタは歩くんだからWalk関数は絶対に必要だよな」と気軽にクラスを作成出来てしまいます。作りながら「あ、スタートポジションは必要だわな」「とりあえずは定義した節に線形に歩くようにしよう」と徐々にその仕様が固まっていくわけです。そのうち「線形だとカクカクしちゃうからスプラインで結ぼうかな」などと欲が出てくるわけですが、それもガシガシと詰め込んでしまいます。そしてある程度作った時に、「まてよ・・・節の結び方はきっと色々考えられるだろうから、この機能だけ別物にしようかな」とか「線形に歩くクラスとスプラインで歩くクラスをサブクラスとする上位クラスを抽出しちゃおうかな」などとリファクタリングによってクラスを綺麗に整理していくわけです。そのようにして出来た「NodeConnectionクラス」とか「Walkingクラス」というのは、他のゲームにも流用ができるほど綺麗なクラスになっているものです。
そうして綺麗にしたクラスは、普通は殆ど触る事がなくなりますのでスタティックライブラリにでもしてインターフェイスを固めてしまえるようになります。こうなると、本格的に再利用可能なクラスのできあがりとなります。
トップダウン方式は確かにかっこいいものです。自分が神様にでもなったかのように、根幹を作りながら徐々に世界を具象化していく。でももし根幹を間違えたら、世界は後戻りできない酷い状態になってしまいます。一方で、ボトムアップ方式は、具象な世界に実際に存在する具象物をみて、抽象できる部分をピックアップするわけで、言ってみれば泥臭い作業です。でも「生き物」がいるから「人・犬・鳥」があるのではなくて、「人・犬・鳥」がいるから「生物」が見えてくる。それは確かな事なはず。
クラスは抽象概念を導出することが大切で、トップダウンもボトムアップも最終的には抽象概念が存在する階層構造にはなります。それならば私は、背伸びをせずに健康的にゲーム製作が出来るボトムアップ方式を強く推したいですね。