こんにちは。ゲームプログラマーのメガネです。
この記事では、Dartのインターフェースを解説します。
インターフェースの前にクラスを理解しておいた方が良いので、先にこちらの記事を読むことをおすすめします。
インターフェースの作成方法
DartではJavaのinterface
のように、直接インターフェースを定義する手段がありません。代わりに抽象クラスのabstract
を利用します。abstract class
を定義して、メソッドの実装を行わずに定義だけすれば、インターフェースになります。
abstract class Animal {
String kind();
}
C++はインターフェースも抽象クラスもすべてclass
なので、DartはJavaとC++の中間みたいな感じですね。
インターフェースの使い方
インターフェースの実装クラスを作るにはimplements
を使用します。abstract class
のメソッドは@override
キーワードを付けて、すべてオーバーライドする必要があります。
class Dog implements Animal {
@override
Sting kind() {
return 'Dog';
}
}
インターフェースは複数実装できる
継承で使うextends
は1つのクラスしか指定できませんが、インターフェースを実装するimplements
は複数のクラスを指定できます。
abstract class Flyable {
void fly();
}
abstract class Animal {
String kind();
}
class Bird implements Animal, Flyable {
}
インターフェースを使うメリットは、クラスの利用者が実装を知らなくてよくなること
インターフェースを一切使わなくても、プログラムは書けます。では、何のためにインターフェースを使うのかというと、クラスの利用者が実装を知らなくてもそのクラスを利用できることに、大きなメリットがあるからです。
SOLID原則の「依存性逆転の原則」を守る
SOLIDの原則をご存知でしょうか?その中に「依存性逆転の原則」があります。
依存性逆転の原則(dependency inversion principle)
High-level modules should not import anything from low-level modules. Both should depend on abstractions (e.g., interfaces), [not] concretions.(上位モジュールはいかなるものも下位モジュールから持ち込んではならない。双方とも具象ではなく、抽象(インターフェースなど)に依存するべきである)
https://ja.wikipedia.org/wiki/SOLID
すごく分かりづらいかもしれませんが……(笑)、モジュール同士を結合させるのではなく、インターフェースを介して依存させた方が良いということです。
なぜ実装ではなくインターフェースに依存すると良いのでしょう?
実装に依存してしまうと、下位モジュールに変更があった際に、上位モジュールもビルドが必要になり、さらに上位モジュールのテストやリリースも必要になります。ひとつの変更が他の多数に影響してしまうのです。
モジュールが多くなるほど、やり取りが多くなるほど、後で何かを変更するときに取り返しのつかないほど膨大なコストにつながる可能性があります。
そのため、モジュール同士を依存させるときには、なるべく変更の可能性が少ないインターフェースに依存させておいた方が、後々コストが低くて済むということですね。
インターフェースを使うことで、実装を簡単に切り替えられる
インターフェースを使うことで、実装を簡単に切り替えることができます。利用者側はインターフェースのメソッドを呼ぶだけなので、実装の中身を知らなくてよく、実装が変わっても何も変更をする必要がないです。
たとえば、スライムをバラモスに変更する場合に、インスタンスを生成する場所の書き換えは必要ですが、使用している箇所は変更不要です。スライムだと思っていたらいつのまにかボスになってた、ということがあり得ます。
これは、テストに有利に働きます。一部のコードをテストコードに容易に置き換えることができます。
インターフェースを最大限活用するにはDI(Dependency Injection)を利用すべし
せっかくインターフェースを利用するのに、利用者側で実装を知っている必要があるとメリットを活かせません。
うまく解決する定石として、DIがあります。
DIは、クラスの利用クラスにインターフェースを注入してあげる仕組みのことです。インスタンスの生成は別の場所で行い、利用クラスはインターフェースを受け取るように設計します。
abstract class Animal {
String kind();
}
import 'animal.dart';
class Dog implements Animal {
@override
Sting kind() {
return 'Dog';
}
}
import 'animal.dart'
/// Animalインターフェースに依存するけど、Dogには依存しない
class Breeder {
Breeder(this._animal);
Animal? _animal;
}
void main() {
final var animal = Dog();
final var breeder = Breeder(animal); // コンストラクタで注入してあげる
}
そうすることで、利用クラス側は実装を何も知らなくてよくなります。
まとめ
インターフェースについて解説しました。
オブジェクト指向の基礎ですので、積極的に活用してみてください。
コメント