オブジェクト指向設計論

download report

Transcript オブジェクト指向設計論

プロジェクト演習Ⅳ インタラクティブゲーム制作 プログラミング4 2011/11/22 オブジェクト指向設計論 &みんなで設計に突っ込みあおう

今日の内容

• どうオブジェクト指向で作るのか – クラスの設計理論 • 包含か、継承か – newの使いどころはどこなのか

なぜ オブジェクト指向で作るのか

• これはもう皆さん理解しているはず – ゲーム内のオブジェクトを、それを構成する変数 と関数をセットにして表現するため – 変数1つで複雑なものが表現でき、変数を量産す れば同じ種類のものを量産できる – ある物事についての処理が隔離できるため、あち こちに処理が分散しなくてすむ • ではこれをどう設計すればいいのか?

皆さんが作る多くのクラスは 「参照オブジェクト」です

• • • 「C++クラス設計のノート」を参照 – http://www.ogis-ri.co.jp/otc/hiroba/technical/CppDesignNote/ シーン、キャラクター、エフェクトなど大抵のゲ ーム内に登場する物は全て該当します 参照オブジェクトを効果的に運用するために 、値オブジェクトを設計することもあります – fk_Textureに対するfk_TexCoordなど

包含関係(Has-a関係)

• • • 難しい表現ですが、ぶっちゃければ 「メンバ変数にする」ことです アプリケーションはWindowを持つ アプリケーションはシーンを持つ – シーンはマップを持つ • マップは地形データを配列で持つ – シーンはキャラを持つ • キャラはモデルを持つ • キャラはミサイルを持つ

メンバ関数にした場合の問題点

• 自分を持っているクラスのメンバを利用でき ない – シーンはキャラを制御できるが、キャラからはシ ーンを参照できない • 解決策:メンバにthisポインタを渡す – 関数レベルでのバインドをおすすめする – ガチガチに結びつける手段もあるが、 必要最低限にとどめた方が良い

関数レベルのバインド

// 飛び道具を表すクラス class Missile { void funcHoge(HogeChara *); }; ///////////////////////////////// //// // キャラを表すクラス class HogeChara { Missile arrow; void update(void); }; { void Missile::funcHoge (HogeChara *argOwner) argOwner->…; } ////////////////////////////// /// } { void HogeChara::update(void) arrow.funcHoge(this);

ガチガチバインド

// 飛び道具を表すクラス class Missile { HogeChara *ownerChara; Missile(HogeChara *); void funcHoge(void); }; ///////////////////////////////// //// // キャラを表すクラス class HogeChara { Missile arrow; HogeChara(void); }; { } Missile::Missile (HogeChara *argOwner) : ownerChara(argOwner) { void Missile::funcHoge(void) ownerChara->…; } } ///////////////////////////////// //// { HogeChara::HogeChara(void) : arrow(this)

ガチガチバインドの問題点

• • • あまりクラス分けした意味がなくなる – 特にfriend宣言付けると無法地帯になるので節度を 持った参照を心がけること オブジェクトの寿命がずれると悲惨 – 別の場所でdeleteされたポインタを持ち続けるケース が発生しやすくなる メンバに引数を渡すのがちょっと面倒 – – ポインタにしてnew/deleteするか、 コンストラクタの初期化子リストを使う • 私も最近まで使うの嫌でしたが、使うようになった

継承関係(Is-a関係)

• 「(子クラス)は(親クラス)の1種である」と言え る場合のみに使うこと! – TestAppはfkut_AppBaseの1種である – タイトル画面はシーンの1種である – ゲームのメイン画面はシーンの1種である – 剣士はジョブの1種である – 魔法使いはジョブの1種である – カプセルは当たり判定の1種である – OBBは当たり判定の1種である

継承の狙い~何がうれしいのか?

• • • OOPを理解する上での最後の障壁 内容が高度なせいもある、要因の1つに 「メリットが多すぎる」ことがある ざっくりと2つに絞って話をする – 差分プログラミング – ポリモフィズム

差分プログラミング

• 既にあるクラスへの付け足しや書き換え – fk_Modelを継承して描画処理を変更するなど • 共通の処理や構造をまとめておき、 状況に合わせて必要なものを付け足す – fkut_AppBaseを継承してプログラムを作成

ポリモフィズム(多態性)

• • 共通点のあるクラスをまとめられる まとめた上で、共通の型で管理できる – 「キャラクター」「シーン」と言った大まかな体系を 表すクラスを継承 – 具体的な子クラスを作成して実際にはそちらを利 用するが、プログラム上では「親クラスの変数」と して扱う • • Parent *obj1 = new ChildA(); Parent *obj2 = new ChildB();

継承を使わなかったら どうなるか?

• 手間はかかるが何とかなる – JavaとかC#だとそうは行かないが… • • 差分プログラミングの代わりに – 毎回全部のメンバを書いたクラスをコピーして書 き換えて使う ポリモフィズムの代わりに – 想定される全ての機能、データを盛り込んだクラ スを作り、オブジェクトごとにモードを切り替えて 使う

継承を使うのは 必要に迫られてからでいい

• 無理して使うと設計がぐちゃぐちゃになって破 綻する • が、使えると色々スッキリするのは事実 – 同じコードをコピペしないで済む – それぞれ別々の変数を用意しなくて済む • スマートになりますよね – 条件分岐がオブジェクト生成時だけで済む • これが一番でかい

new/deleteが必要な場所

• • • • オブジェクトが作られるタイミング以外で新た なオブジェクトを生成したい場合 – 「クラスのインスタンスが生成された時」と「スコー プに入った時」以外 スコープの枠を超えた寿命を持つオブジェクト を作りたい場合 スタックのサイズを超える配列を作りたい場 合 可変長オブジェクト配列を作りたい場合

解体責任をはっきりさせること

• • • コンストラクタでnewしたらデストラクタで始末 する ポインタ変数はNULLで初期化しておく – newするときはNULLチェックして、 多重new(メモリリーク一直線)を防ぐ – deleteするときはポインタにNULLを代入 解体場所がずれる設計をする場合は、 必ずその場所をコメントに残す

デザインパターンとの 付き合い方

• • 実は今日話した内容のいくつかは、デザインパ ターンのいくつかに当てはまります – 実践的な設計の経験が無い状態で眺めてみても、あ まり意味がありません あれはプログラミング中級者以上の「あるある集 」だと思いましょう – ある程度分かるようになったら中級者の証 – その勢いで他のパターンにも手を出して試してみる のが吉