【図解】RPGで攻略するオブジェクト指向の正体
タイトルにもある通り今回はオブジェクト指向について取り上げようと思います。
大学時代は情報系の学部だったのですが4回生の研究室ではSmalltalkを使ったオブジェクト指向の講義が毎週行われていました。すごく面白い教授で、プログラムだけではなく日常生活からもオブジェクト指向みたいなものひたすら例えてきて私もすっかりその習慣にはまってしまいました。
ですが、意外とこれが生活習慣の向上につながる...?こともあったので是非とも皆様にも私と同じ習慣を身に付けてほしいです。(慣れると、コード設計の視点が日常に溶け込んでくるのでおすすめですよ!)
我々エンジニアにとって避けては通れない道だと思っているので、ぜひとも見つけてみてください。
0.前書き
私がオブジェクト指向に出会った時に躓いた点としては以下のようなところでした。
- 手続き型プログラム(上から下へひたすらコードを書く)からオブジェクト指向型プログラム(モデル、手続きをオブジェクトごとに定義する)への移行
- Java, Python, Ruby, TypeScriptと言語によってコンストラクタの書き方や概念が異なる
これらは周りの多くの人が研修などで躓いていた印象です。
ですが、個人的にプログラムを**「現実世界のモノ(オブジェクト)」として整理整頓することで理解は進むのではないかと考えています。皆様もぜひ、こう例えられるのではないかという意見があればコメントしてくれると嬉しいです。
今回は、かなり無難かもしれませんがRPGのキャラクター作りを例に、どの言語でも一貫して使える「4大原則」をビジュアルで理解していきましょう。
1. クラスとインスタンス:設計図と実体
まずは全ての土台となる概念です。
クラスは 「共通のルールや項目を決めた設計図」 です。
インスタンスはクラスを具体化した実体そのものです。
よく皆様が人に物事を説明する時に抽象的な概念を説明するとします。
その時に、相手がまだ理解できてなさそうなら 「例えば」 という文言をつけて、あの後に具体的なものを提示すると思います。あの「具体的なもの」を提示するプロセスこそ、オブジェクト指向における「インスタンス化」に近いイメージです。
RPGのキャラに例えると以下のような形です。
// kotlinコードのイメージ
val hero = Warrior("アルス", hp=120, strength=85)
val knight = Warrior("シルバ", hp=180, strength=60)
val boss = Warrior("ダクロン", hp=500, strength=200)
hero.attack() // 同じメソッドを呼べる
boss.attack() // でも strength が違うので結果も違う
2. カプセル化:データを守るバリア
オブジェクト指向の重要な守備の要、それが 「カプセル化」 です。
なぜバリアが必要か?
キャラの「HP」という大事なデータを、誰でもどこからでも HP = 0 と書き換えられたら、ゲームバランスは一瞬で崩壊します。(チートじゃん...)
-
バリアの中(データ)
- 棒人間(キャラ)と、そのステータス(HPなど)。
-
バリアの表面(メソッド)
- 「ダメージを受ける」「回復する」といった、外部に公開されたボタン。
カプセル化の本質
- データ(棒人間)をカプセルの中に隠す。
-
用意されたボタン(メソッド)を通したときだけ、内部で安全に処理が行われるようにする。
これがカプセル化の正体です。
3. 継承:ピラミッドで「基本機能」を使い回す
効率よくキャラクターのバリエーションを増やすための知恵が 「継承」 です。
差分だけを作ればいい
一から「魔法使い」や「戦士」を作るのは大変です。そこで、共通の「キャラクター」という親クラスを作ります。
-
ピラミッドの頂点(親クラス:Character)
- 全キャラ共通の「名前」「HP」「歩く」という性質を持つ。
-
ピラミッドの下段(子クラス:Wizard / Warrior)
- 親の機能を100%引き継ぐ。
- その上で、自分だけの「魔法」や「剣技」を追加する。
継承のメリット
- ピラミッドの下に行くほど、機能が具体的になる。
-
「親ができることは、子も当然できる」 という状態を作る。
これによって、開発の重複(二度手間)を劇的に減らすことができます。
コラム:継承と集約の違い
今回はメインで説明していませんが、オブジェクト間の関係を示す概念として
集約という概念もあります。継承と集約は以下のように区別できます。
継承(Inheritance):
「AはBの一種である(Is-a関係)」という、人間や魔族みたいなもの。
集約(Aggregation):
「AはBを持っている(Has-a関係)」という、取り外し可能な装備品のようなもの。
(例:剣士は剣を持ち、魔法使いは魔法の杖を持つ)
もし「戦士」に「剣」の機能を持たせたい時、間違って「継承」を使ってしまうと……
class 戦士 extends 剣 (戦士は剣の一種である)という、人間なのか武器なのか分からないシュールな構造になってしまいます。
一方、「集約」を使えば、戦士というオブジェクトの中に「剣オブジェクト」を持たせるだけで済みます。
4. ポリモーフィズム:同じ指示で違う動き
最後に、同じ「攻撃」という合図で、キャラごとに個性を出す仕組み 「ポリモーフィズム(多態性)」 です。
パーティ全員に attack()(攻撃しろ)という命令を出した場面を想像してください。
- 剣士:Warriorは、剣で斬りかかる。
- 魔法使い:Wizardは、杖を振って火を放つ。
- 格闘家:Fighterは、拳を叩き込む。
なぜこれが嬉しいのか?
- 呼び出す名前(メソッド名)は同じ。
- でも、受け取る相手(インスタンス)によって、自動的に適切な振る舞いに切り替わる。
- 新しい職業が増えても、命令を出す側(メインプログラム)は「全員、攻撃しろ!」と一言言うだけで済むようになります。
まとめ:言語が変わっても「図」は変わらない
いかがでしたでしょうか?
- クラス は設計図、インスタンスは実体。
- カプセル化は、ステータスを勝手にいじらせないバリア。
- 継承は、ピラミッド構造で機能を使い回す。
- ポリモーフィズムは、同じ命令でキャラごとの個性を出す。
まずはこのビジュアルを大切に、コードを書いてみてください!
また、身の回りのものに対してこれはクラス...これはインスタンス...みたいにかんがえてみるのも良いかもしれません。
コラム:なぜ「パラダイム」を学ぶのか
これは自論ですがプログラミングというのは言語によって文法は異なりますが、
どの言語でもオブジェクト指向のようなパラダイムを理解しているかしていないかで言語学習の効率さがかなり変わってくると思います。
文法だけを覚えるのは「単語の暗記」に近いですが、パラダイムを理解することは「思考のOS」をインストールすることに近いからです。一度このOSが入ってしまえば、新しい言語に出会っても「この言語ではどう表現するのか?」という視点で、驚くほどスムーズに習得できるようになります。
追記:
継承に対してさらに深堀下内容を投稿したのでこちらもお読みいただけると嬉しいです!



