【初心者向け】継承・多態性について

ども、ゴコーです。

今回はオブジェクト指向の大事な概念、継承・多態性についてです。
後で出てくる多態性の部分が初心者の方にはちょっと理解し辛いかもしれません。全く知らない方は実際に手を動かしつつ記事を追ってみるといいかもしれません。

  • 継承とは
  • 継承で基底クラスと同じ動作をさせる
  • 多態性とは
  • 多態性で派生クラスに異なる動作をさせる
  • 活用例
スポンサーリンク

継承とは

継承(けいしょう、inheritance:インヘリタンス)とはオブジェクト指向を構成する概念の一つである。あるオブジェクトが他のオブジェクトの特性を引き継ぐ場合、両者の間に「継承関係」があると言われる – ウィキペディアより

継承は C#, Java などのクラス概念のあるプログラミング言語で使える概念です。継承元のクラスを基底クラス、継承先のクラスを派生クラスなどと呼びます。これを使えば、別々のクラスに同じ処理を何度も書く手間が省けたり、使う側が別のクラスと意識せずに使いまわせたりします。

ではそれぞれについて見ていきましょう。

継承で基底クラスと同じ動作をさせる

まずは具体例を挙げてみます。 RPG を作ることになったとします。そして、以下のような2種類のキャラクターを作ることになりました。

  • HP を持ち、ダメージを与えられるキャラクター
  • HP を持ち、ダメージを与えられて、回復もできるキャラクター

1つ目のキャラクターをコードで表現すると以下のようになります。

get, set が何してるか分からない!という方は 変数ではなくプロパティを公開する を一読してください。Assert は、雑な表現ですが Debug.Log() みたいなものだと思ってください。機会があればまた今度紹介します。

次に2つ目の、 HP を持ち、ダメージを与えられて、回復できるキャラクター をコードで表現します。

どうでしょうか、先ほどの CharacterBase のコードと比べてほとんど差がないのが分かるかと思います。実際 Heal() しか増えていません。継承を使うことで、こうしたコードの重複をなくすことができます。

継承させる場合以下のように記述します。

この場合 Base クラスの持つ要素が Sub クラスに継承されます。

では、 Human を継承を使って書き直したいと思います。

CharacterBase を継承することで、CharacterBase で書いた変数、関数などをそのまますべて使うことができます。継承すればその上に機能を追加できるため Human は体力の要素を持ちダメージを与えられて、さらに Heal() で体力を回復できます。

しかしこのままでは ‘CharacterBase.hp’ is inaccessible due to its protection level というエラーが出ると思います。これは CharacterBase の hp が private に設定されていて Human からアクセスできないからです。

ということで CharacterBase の hp のアクセシビリティを変更します。 hp を以下のように変更してください。

private はクラス内でのみ使用でき、 public はどのクラスからでも使用できる設定ですが、 protected はクラス内・継承先でのみ使用できるようにする設定です。

これでエラーが消えたと思います。継承先でも変更・参照できるようにしたい変数や実行できるようにしたい関数などは protected に設定するようにしてください。

多態性とは

ポリモーフィズムあるいはポリモルフィズム(Polymorphism)とは、プログラミング言語の型システムの性質を表すもので、プログラミング言語の各要素(定数、変数、式、オブジェクト、関数、メソッドなど)についてそれらが複数の型に属することを許すという性質を指す – ウィキペディアより

ポリモーフィズム、あるいは多態性(たたいせい)などと呼びます。非常に重要で便利なのですが、継承同様なんともピンとこない概念です。今回も具体例を挙げつつ説明してみたいと思います。

多態性で派生クラスに異なる動作をさせる

先ほどのキャラクターに加えて新しく、回復すれば逆にダメージを受けるゾンビキャラクターを追加することになったとします。まずは多態性は使わず実装してみます。

この場合も、先ほどと同じく CharacterBase を継承し、大部分を省略することはできています。

ではここで、実際に他のクラスから利用してみましょう。
ドラ○エ の ベホ○ラー 的な全体回復魔法メソッドでシーンに存在する全キャラクターを回復させましょう。

inheritance0

この図は Human を3つ、 Zombie を2つ、 Damage Applier を1つ、それぞれアタッチしたオブジェクトを作成・実行した結果です。スクリプト通りの結果になっているのが確認できると思います。

普通に書くと先ほどのコードのようになり、結果はこのままでも問題ないのですが、このままではよくない点があります。

それは回復機能を持つクラスが増える度に Multiheal() を編集しなくてはいけないということです。

めんどくさいですよね。それほどでもないと思う人は、規模が百倍くらいに増えた時のことを想像してください。修正個数も百倍です。

間違いなくめんどくさいですよね?

一方、理想は以下のようなコードを一度書いたらよほどのことがない限り変更しないことです。

一括で処理できています。このように回復機能を持つものすべてをまとめて処理できれば、いくら種類が増えても同様に一括処理をすることもできます。

さて、理想を実現する方法ですが、それが先ほど書いた多態性です。

先ほどの節であった説明だけでは分からないので、具体的に変更を加えつつ説明しようと思います。今回の場合、手順は以下のとおり。

  1. まとめて回復させたいものは Human を継承させる
  2. Human の Heal() を virtual にする
  3. Zombie の Heal() に override をつけて処理を上書き

1. 回復させたいものは Human を継承させる

まずは、あとでまとめて処理させる元(今回は Human )を継承させます。

こうなります。このままでも Heal() を呼ぶことはできます。以下のコードで呼んでみます。

inheritance2

この通り、当たり前ですがこのままではただ Human を継承しているだけなので Heal() を呼んでも Human で定義してある通りに回復してしまいます。

2. Human の Heal() を virtual にする

次に Human の Heal() に virtual 修飾子をつけます。

virtual は「継承先で上書き(override)されてたらそっちを使ってね!」という意味の修飾子です。まだ訳が分からないかもしれません。あとで説明するのでひとまず次に進みます。

3. Zombie の Heal() に override をつけて処理を上書き

先ほど Human を継承した Zombie ですが、こちらで Heal() を上書きします。

コードは以下のようになります。

上書きする場合は override 修飾子を付け、名前は同じものを使用します。

では改めて先ほどの Multiheal() を呼んでみます。結果は…。

inheritance3

Human の Heal() を呼んでいるのに回復とダメージの両方が発生しました!

Zombie は Human を継承しているので Heal() を呼んだ場合、通常は先ほどと同じく Human の Heal() が呼ばれるのですが、 Human の Heal() は virtual 修飾子を付けていて書き換えを許可されていました。さらに Zombie の Heal() にも override をつけて書き換えることを宣言しているのでこの場合 Zombie の Heal() が呼ばれました。

他にも、 Heal() を呼んだ時に指定された値より増やして回復したいなど、違う挙動をさせたい場合も Human を継承して継承先で override を付けるだけで呼びだし側を変更せず一括して呼び出すことができます。

ざっくり言ってしまえば、多態性とはこのように「同じ関数を呼び出しているのに異なるオブジェクトの異なる関数を実行できる仕組み」です。

利点は先ほどからの例のとおり呼び出される側の種類が増えても呼び出し側の変更が必要ないことですね。大規模なゲームになるにつれ効いてくると思います。

活用例

最後に継承・多態性の活用例を紹介して終わります。

どちらもすごく身近な例を出そうと思います。

継承の活用例

先ほどの例で使用した CharacterBase とそれを継承している Human, Zombie や、シーンに存在するオブジェクトのコンポーネントは(ほぼ)すべて MonoBehaviour を継承しています。

これによって得られる利点や、逆に制限されることもあるのですが、普段間違いなく使っている Start() や Update() などのコールバックも MonoBehaviour を継承しているからこそ使用できています。

多態性の活用例

コールバック以外にもよく使う関数はあると思います。そのうちの1つが OnCollisionEnter(), OnTriggerEnter() などコライダー関係の関数です。

普段は何気なく使っていると思いますが、コライダーにはボックス、スフィア、カプセルなど様々な種類があり、衝突の計算式は形状が違うので当然違います。

しかし OnCollisionEnter() などを使うときはそうした形状の違いを意識する必要はまったくありません。これは多態性のおかげです。

以上、継承と多態性についてでした。

最後の例でも分かったかもしれませんが、2つとも非常に重要で実は普段お世話になりっぱなしの概念です。使える場面では積極的に利用してみてください!

次回はこれらの概念を使ってインターフェイスについて触れたいと思います。本命です。

では、また。

Comments

comments

スポンサーリンク
336*280px

コメント

  1. @combatsheep より:

    なんてことだ。10年以上面倒くさいと避けてきた継承がこんなに簡単だったとは。。。
    https://t.co/2YfoyT2HHb
    https://t.co/FmXG1Uio9x
    https://t.co/EsIqHWiUnr
    https://t.co/0FFWCEFsQt