最近、クラス書けば何でもオブジェクト指向だと思ってる人、多くないですか?
そんな人たちに小学生でもわかるくらいの内容で、
主観混じりでオブジェクト指向を自己流解説してみます。
語彙力低めなので少し分かりづらい部分があるかもですが、ご了承ください。
レベル1「現実での考え方」
まずはかなり噛み砕いた説明をします。
「オブジェクト」の意味は色々ありますが、ここでは「物体」で説明します。
「物体」には「生産」「動作」「変化」「破壊」の、基本的な4つの変動が生じます。
これらの変動は「物体」の性質毎に振る舞いの内容が違います。
「ロボット」で表すと、下記のようになります。
- 【生産】ロボットが作られる
- 【動作】ロボットが命令によって前進する
- 【変化】ロボットが経年劣化を起こす
- 【破壊】ロボットが爆発して消滅
次に「動物」で表すと、下記のようになります。
- 【生産】動物が生まれる
- 【動作】動物が前に歩く
- 【変化】動物が歳を取っていく
- 【破壊】動物が死ぬ
変動の本質は似ているけど、振る舞いが全く違います。
これらの振る舞いをモノの性質毎に分けて処理する枠組みを**「オブジェクト指向」**で構築します。
レベル2「ゲーム的な考え方」
皆さんご存じテレビゲーム。
その画面上に表現されているモノは全て**「オブジェクト」**です。
ゲーム上の基本的な表示物の4つの変動を見てみましょう。
対象物 | 生産 | 動作 | 変化 | 破壊 |
---|---|---|---|---|
キャラクター | 出現 | 操作、AI行動 | ライフ増減、レベルUP | 退場、死亡 |
文字情報 | 表示 | 表示アニメーション | 色、フォント | 非表示 |
背景 | 表示 | スクロール | 色、時間に伴う変化 | 非表示 |
これらを「物体」として管理する枠組みが「オブジェクト指向」であり、
ゲームそのものが「オブジェクト指向」で動いていると言えます。
ゲーム開発に携わる人は、共通してオブジェクト指向の理解が必須ですね。
レベル3「共通点や違いでの考え方」
どんな環境においても、物体が共通して持っている属性がいくつかあります。
- 識別子(名前等)
- 位置(三次元)
- 大きさ
- 目に見えるか見えないか
- etc...
これら共通する属性をAとしましょう。
次に動物と機械の違いを考えましょう。
(ゲーム上だと色々な事が想定出来ますが、ここでは現実世界を例としてあげます)
- 動物は声、機械は電子音(音を出す)・・・①
- 動物は心臓、機械はCPU(活動する)・・・②
- 動物は、機械は・・・etc...
現象は違えど、本質は同じである事がなんとなく分かります。
これらの違いをBとしましょう。
次にそれらの違いは外見で判断できるかを考えます。
Bの①②に注目します。
- ①動物は声帯で音を出す、機械はスピーカーで音を出す
- ②動物の心臓は血圧で動く、機械のCPUは電圧で動く
本質は同じだけど、仕組みが違います。
この違いをCとします。
レベル4「オブジェクト指向三原則」
オブジェクト指向三原則といわれる**「継承」「ポリモーフィズム」「カプセル化」**。
お気付きの方も居ると思いますが、実はレベル3で説明した要素ABCはこれに当て嵌まります。
- A:共通する属性の項目を引き継ぐ:継承
- B:本質は同じだが異なる振る舞いを持つ多態性:ポリモーフィズム
- C:外部から見えない仕組み:カプセル化
レベル5「プログラムを書いてみる」
ここまできてやっとプログラムを書きますが、今回はC++っぽい記法で書きます。
(自分がC++書いてた時期が学生時代で8年前の為・・・。
動作検証もしませんので、間違いが有ったらご指摘ください。)
オブジェクト指向プログラミングにはクラスの理解がほぼ必須です。
どの言語のクラスでも理解していれば下記例は理解出来るはずですので、
今自分が勉強したい言語のクラスの仕様は把握してください。
どの言語にも**「クラスの継承」と「メソッドの上書き」**くらいはあるので、このワードで調べてください。
クラスの無い言語を扱う場合でも、データの持つ性質毎に振る舞いを変えればオブジェクト指向は実現可能です。
ガッツリやると長いので、簡単な文字を出力するだけの*「犬と猿の違い」*で表現してみます。
前提として、例に使う下記のクラス/関数/マクロを説明します。
名称 | クラス/関数/マクロ | 説明 |
---|---|---|
String | クラス | 文字列処理を簡単にします。 |
関数 | 文字列を出力します。 | |
strMarge | 関数 | 二つ以上の文字列を結合します。 |
std::shared_ptr | stdライブラリ | スマートポインターを使用します。 |
class Animal{
private:
String name;
String voice;
String food;
protected:
void init(const String& in_name, const String& in_voice, const String& in_food)
{
this->name = in_name;
this->voice = in_voice;
this->food = in_food;
}
virtual ~Animal() {}
public:
String getName() { return this->name; }
String getVoice() { return this->voice; }
String getFood() { return this->food; }
virtual void actionRun(){} // 継承先で上書き
virtual void actionTalk(){} // 継承先で上書き
virtual void actionEat(){} // 継承先で上書き
};
class Dog : public Animal{
public:
Dog(){ this->init("犬", "ワン!ワン!", "骨"); }
virtual void actionRun() override { print(strMarge(this->getName(), "は足4本を使って歩きます")); }
virtual void actionTalk() override { print(strMarge(this->getName(), "は", this->getVoice(), "と吠えます")); }
virtual void actionEat() override { print(strMarge(this->getName(), "は", this->getFood(), "をゴリゴリと食べます")); }
};
class Monkey : public Animal{
public:
Monkey(){ this->init("猿", "キー!キー!", "バナナ"); }
virtual void actionRun() override { print(strMarge(this->getName(), "は手足を使って歩きます")); }
virtual void actionTalk() override { print(strMarge(this->getName(), "は", this->getVoice(), "と鳴きます")); }
virtual void actionEat() override { print(strMarge(this->getName(), "は", this->getFood(), "を手で剥いて食べます")); }
};
int main()
{
std::shared_ptr<Animal> dog = std::make_shared<Dog>();
std::shared_ptr<Animal> monkey = std::make_shared<Monkey>();
dog->actionRun(); //犬は足4本を使って歩きます
dog->actionTalk(); //犬はワン!ワン!と吠えます
dog->actionEat(); //犬は骨をゴリゴリと食べます
monkey->actionRun(); //猿は手足を使って歩きます
monkey->actionTalk(); //猿はキー!キー!と鳴きます
monkey->actionEat(); //猿はバナナを手で剥いて食べます
return 0;
}
型がAnimalでも、**実体(インスタンス)**がDog、Monkeyなので、出力内容が違いました。
上記コードの要点を箇条書きで纏めます。
- ①DogとMonkeyの各クラス宣言時、共通の属性を管理するクラスAnimalを継承しています。
- 初期化の時に、共通の属性をそれぞれ初期化しています。
- ②Animalの仮想関数3つをそれぞれのクラスで上書き(オーバーライド)し振る舞いを別々に実装します。(ポリモーフィズム)
- ③main関数でそれぞれ共通のメソッドを呼び出しましたが、何も把握せずとも別々の文章が出力されました。(カプセル化)
①②③がそれぞれ出来ていたら、オブジェクト指向の枠組みの完成です。
まとめ
実際のゲームとなると、レベル5の例で書いたそれぞれのクラスメソッドは、
文字出力等の簡単な内容ではなく、アニメーション変更やステータスの変化が伴い、更に複雑な仕組みとなるでしょう。
その複雑な仕組みをクラスで分けて外部から振る舞いを隠蔽し、
実装スピード向上やリーダブルなソースコードの実装を自然に行える事が、
オブジェクト指向の絶対的アドバンテージなのです。