プログラムの設計を考える
プログラムを組む際、そのプログラムが何をするかを考え、その手順をプログラムに記述していきます。オブジェクト指向では、クラス(基本@)で触れたように細かなプログラムの部品をたくさん作り、それを連絡させて一つのプログラムを構成していきます。そのプログラムの部品をどのように定義し、どのように連携させていくかが設計であり、オブジェクト指向の一番重要なところになるでしょう。設計の仕方は色々あり、素晴らしい設計法は形式化さえされていますが、初めのうちは製作者の頭の中で思い描く動作をそのままオブジェクト設計の置き換えていくのがいいと思います。
オブジェクト指向っぽい設計のイメージ
漠然と抽象的なことを言っていても仕方がないので、簡単な例を挙げてみます。一応ゲームプログラム講座ということで、シューティングゲームの処理の流れを考えてみます。下の図は、プログラムの流れのイメージを図に示したもので、左が手続き型っぽく一本の流れを描いたもの、右がオブジェクト指向っぽい感じの流れを描いたものです。
図が思いのほかうまくかけなかったので、補足説明を。手続き形式では、敵と味方のパラメータをグローバル変数として書いておき、それをいろいろな関数を呼び出して処理していきます。プログラマーは全体の流れを考えながら色々とプログラムを組まなくてはなりません。一方、オブジェクト指向でもこの流れは基本的に変わらないですが、機能をクラスにまとめることによって、オブジェクトそのものに仕事を依頼するイメージに変わります。つまり、p.OnMove()とメソッドを呼び出すことで、pの移動処理が行われるのです。OnMoveはPlayerクラス内のことだけ考えてプログラムすればいいので、考えなければならない範囲が狭くなりますね。こうやって、機能とデータをパッケージングして分割していくのがオブジェクト指向っぽい設計の仕方になります。
オブジェクト間の連絡のさせ方も重要ですが、それ以上にどの機能をオブジェクトに置き換えるかが重要です。管理者の頭の中では、ゲーム中に存在する様々なものを一つのオブジェクトとして捕らえています。Enemyは敵機の機能を持ったオブジェクトで、Playerは自機の機能をもったオブジェクトというように、頭の中でそれぞれの機能を分割し、そのように記述していきます。これ以降はこのイメージを例として説明を進めていきます。
集合の概念
さて、設計のイメージをつかんでもらったところで、ちょっと違った見方をしましょう。上の図では、敵戦闘機を表すEnemyクラスと自機を表すPlayerクラスがあります。この二つは違った機能を持ったものですが、弾を打つ、移動するなどの共通の機能も持っています。上の図でも、OnMoveやOnDrawなどプログラム的に共通の性質がありますね。なぜなら、この二つは戦闘機という共通の性質を持っているからです。
現実世界には、ものごとに名前をつける場合に集合の概念を用いた階層構造を利用する場合が多々あります。「〜大学〜学部〜専攻の〜です。」なんて自己紹介をすることがありますね。先ほどの例でも、Enemyクラスは「戦闘機の敵戦闘機です」Playerクラスは「戦闘機の自機クラスです」と言うことができます。この紹介を聞けば、誰もがPlayerは戦闘機なんだということが分かり、戦闘機の機能を有していることも分かります。集合の概念を用いれば、初めからそのオブジェクトの機能を全て考えるのではなく、もっと大きな集合から段階的に細かく分化して機能を考えていくことができます。
集合を記述する機能 - 継承 -
なんと便利なことに、オブジェクト指向にはこの集合の概念を記述する機能が備わっています。それが継承です。継承を用いれば、継承もとのクラスの機能を受け継いで、さらに拡張させた新たなクラスを定義することができます。階層的な定義と同じですね。今回のシューティングの例では、戦闘機を表すCombat型クラスを定義し、それを継承してEnemyクラスとPlayerクラスを定義することができます。そうすれば、EnemyもPlayerは戦闘機の機能であるCombatクラスの機能を持つことになります。
継承に関しても、いくつかの用語が出てきます。まず、あるクラスから継承したクラスを派生クラスといいます。EnemyはCombatの継承クラスです。逆に、派生元となるクラスのことを基本クラスといいます。CombatはPlayerの基本クラスです。
継承
さて、概念と意義を理解してもらったところで、C#の継承の記述の仕方を紹介します。
public class クラス名 : 継承もとのクラス名 { } |
継承を使い、Combat、Player、Enemyを定義したのが次のコードです。メソッドの内容まで書くのは面倒なので、メソッド名とその機能のコメントだけを記述しておきます。
public class Combat { public void Move(int X, int Y); //移動 public void Shot(); //レーザーを打つ public void OnDraw//描画 } public class Enemy : Combat { public void AI(); //AI制御 } public class Player : Combat { public void KeyControl(); //キーコントロール } |
このように、派生したクラスは基本クラスのメンバも含むことになります。よって、「Player pl = new Player(); pl.Shot();」なんてコードを書いてもちゃんと実行されるのです。ShotやOnDrawメソッドをPlayer,Enemyそれぞれに書く必要もなくなり手間も半減しますね。
設計的な面から見た継承の利点は、クラスの階層構造をすっきりと表せることでしょう。では、コーディング負担の面から利点を考えてみます。長いプログラムを書いていると、同じコードが何回も出てくることがよくあります。もし、あとからその部分を修正することになると、全部の箇所を直さないといけませんね。2つならまだ我慢できますが、4,5箇所になるともうやる気がおきません。そういう事態にならないためにも、同じ機能を持つクラスはこのように基本クラスから派生させると便利になりますし、もちろんコードを記述する負担も減ることになります。
まとめ
継承は、同じコードを何度も書く手間を省くことのできる便利な機能です。しかしながら、それ以上にクラス間の属性を明確に記述できる利点があります。今回はその利点を中心に解説を進めてみました。「継承はクラスの属性を記述するもの」ということを感じていただければ幸いです。
次回は、メンバのアクセサビリティの話をします。これは、カプセル化の概念を実現する機能です。継承をやらないと解説できなかった&アクセサビリティが分からないと、継承のさらに便利な機能も解説できないということで、次回はこれをやります。