【初心者向け】インターフェイスについて

ども、ゴコーです。

以前 継承・多態性について という記事を書きました。
今回は Interface(インターフェイス) についてです。
ぼく自身、今回の記事はどう説明したらいいか非常に悩みながら書いたので、おかしい点などがあれば是非ご指摘ください!

目次

・インターフェイスとは
・具体例
・継承じゃ駄目なの?
・インターフェイスの注意点
・使用例

スポンサーリンク

インターフェイスとは

インターフェイスとは何か、 MSDN のリファレンスを調べてみると以下のように書かれています。

インターフェイスには、メソッド、プロパティ、イベント、またはインデクサーのシグネチャのみが含まれています。 インターフェイスを実装するクラスまたは構造体は、インターフェイス定義で指定されているインターフェイスのメンバーを実装する必要があります。

よく分かりませんね。

現時点ではよく分からないと思いますが、インターフェイスは「クラス(構造体)がどういう名前のメソッドやプロパティを実装しているか」という「約束」です。

約束しているので、インターフェイスを継承した場合、そのインターフェイスのメソッドを実装しないと怒られてしまいます。

InterfaceSample - Microsoft Visual Studio 2016-08-09 21.15.35

InterfaceSample - Microsoft Visual Studio 2016-08-09 21.22.53

表示されるエラーはメソッドを書けば消えました。

まずはそういうものなんだなと思って次に進みましょう。

具体例

アクションゲームを作っているとします。登場するのはプレイヤーと、敵役のゾンビと、フィールドに散らばる木箱です。
それぞれがHPを持ち、攻撃によってダメージを受けます。攻撃は ApllyDamage() という関数を呼び出して行うことにします

通常、各クラスの ApplyDamage() を呼び出す場合は以下のようになります。

ダメージを与えるオブジェクトを全て取得して関数を呼び出しています。

それがインターフェイスを使うとこうなります。

これはダメージを与えられるもの用のインターフェイスです。どういう名前の関数、プロパティを実装するかの約束なので、実際の処理は書けません。実際の処理はインターフェイスを継承した側で行います。

こちらのインターフェイスをプレイヤー、ゾンビ、木箱が全て継承しているとします。

プレイヤー、ゾンビ、木箱が IDamageable を継承するということは、IDamageable の持つ ApplyDamage() を実装する約束をするということです。
裏を返せば、 IDamageable を継承しているクラスはどのようなクラスでも ApplyDamage() を持っているということです。

そのため、上記コードのように全く関連のないクラスに対しても一括で処理ができた訳ですね。先ほどのコードの場合、プレイヤー、ゾンビ、木箱以外にダメージを与えるオブジェクトが増えた場合ダメージを与えられる側は勿論、与える側も変更しなくてはいけないのに対して、上記の呼び出し方の場合呼び出し側を変更する必要はなくなり便利です。

コラム
後半の findObjectsOfInterface() は FindObjectsOfType() のインターフェイス用です。シーン内から指定したコンポーネントを持つオブジェクトを複数探す FindObjectsOfType() はUnity5.3.6現在、 UnityEngine.Object を継承しているものにしか使えない仕様です。
一方、GetComponent() はインターフェイスにも使えます。そのため、 MonoBehaviour を全て取得した上でそれぞれに対して GetComponent() をすればインターフェイスを実装しているコンポーネントを取得できます。
本当は静的関数として用意するべきものですが、短くするために今回はまとめてます。

さて、ここまでインターフェイスで呼び出しをまとめられるということを書いてきましたが、インターフェイスの役割は振る舞いの共通化であり、コードの再利用ではないことに注意してください。これについては後ほど説明します。

それって継承じゃダメなの?

前回 継承についての記事を書きました が、そちらの virtual でも似たようなことはできましたよね。じゃあ継承でいいじゃないか、というところですが継承では厳しいところもあります。

例を挙げてみます。

まずはインターフェイスを使わずにクラスの継承でプレイヤー、ゾンビ、木箱にダメージ適用処理を持たせてみます。

これで ReceiveDamageObject を探して実行すれば、まとめて処理できますね。実装内容を変更したい場合も ApplyDamage() を上書きすればオッケーです。

では次に、ダメージ処理はそのままに即死処理というものを追加しましょう。残HPに関係なく死ぬ ザ○ 的なものを食らったときの処理です。ただし、プレイヤーとゾンビだけにです。
さてこうなると少し困ります。処理を追加したい場合は既に継承している親クラスを編集するか、親を複数継承させるか、継承を多層化させるか、という話になるからです。

まず親を編集して即死関数を追加した場合、追加したくない木箱にも機能が追加されてしまいます。ということでこれは却下。
次に、複数の親を継承させるというのは、Unity というより C# では複数のクラスの継承はできないという理由でできません。
最後の継承を多層化させるというのが答えのようですが、これだと継承関係が複雑になりすぎる等のことが起こります。

InterfaceClass01

InterfaceClass02

先ほどまでの処理をそれぞれクラス図に書くとこうなります。
では、さらにこの状態でゾンビと木箱だけに共通の処理を持たせたい場合はというと…?

InterfaceClass03

こうなりますが、結局 Zombie が複数の親を持つことになるのでアウトです。

そこでインターフェイスです。
インターフェイスの場合、持たせたい機能のインターフェイスを継承させるだけでまとめて扱うことができます。
複数継承させることができる点がクラスの継承と違うので、今回のようなケースにも対応できます。

このようにすれば、 DoDeath() を実行しても木箱には何も起こらず意図した結果になります。

インターフェイスの注意点

・振る舞いの共通化 ≠ コードの再利用

先ほどの例ではプレイヤー、ゾンビ、木箱の全てに ApplyDamage() を実装させていましたが、これは全く同じ内容を書いているので無駄ですね。
インターフェイスの役割は振る舞いの共通化であり、コードの再利用ではない、というのはこのように「同じように扱えるけれど、同じ内容をまとめることはできなくなる」ということです。
インターフェイスに処理は書けないのでこうなったわけですが、どうしたらいいでしょうか?

基本的には、共通の処理が多ければ継承主導で、関連が薄い場合はインターフェイス主導でやるのがいいかなあという気がしています。継承とインターフェイスを絡めて使うこともできますが…?

ご意見お待ちしてます!

・後からインターフェイスを変更すると継承するすべてのクラス(構造体)にかなり影響する

インターフェイスは約束事であり破れば怒られる、つまりインターフェイスに定義した関数は継承した側で実装しないとエラーが出るという話を前半で書きました。
これは途中から変更した場合も同様です。インターフェイスの変更によって新しく定義されたメソッド(プロパティ)は実装する側で全て実装しなければいけません。そのため、一度定義したインターフェイスの変更は慎重に行ってください。
もしくは別のインターフェイスを定義してそちらを新たに継承させるというのもありかもしれません。

使用例

以前 Update を高速にするためのマネージャーを作る という記事を書きました。
こちらでは IUpdatable というものを継承し、 UpdateMe() の中に処理を書けば Update のタイミングで処理される仕組みになっていました。
インターフェイスを使用すれば、このように全く関係のないクラスでもまとめて扱うことができます。

継承とインターフェイス、上手く使い分けたいですね!
では、また。

Comments

comments

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

コメント

  1. @veltina_soft より:

    RT @backlight1144: 新しい記事です: 【初心者向け】インターフェイスについて https://t.co/6znxqDA9jy by Unity道しるべ