Q. Javaのinterfaceって結局何が嬉しいの?
classの継承でいいじゃん
会社でそんな話になった。個人的にはinterfaceの有用性を理解しているつもりだったけど、考えてみたら自身で思っているほど理解していなかったので。改めて考えてみた。以下interfaceの意義(?)。
(結論) interfaceは手続きを共通化したものである
・interfaceは定義により手続きを共通化
・classは継承により処理を共通化
と言う感じで、共通化している視点・内容が異なる。また、interfaceはシンプルで抽象度が高いため、classとは異なる形で共通化及び抽象化が行える。
高級言語のパラダイムでは、こういった抽象度に沿って意図・表現・定義などを重要視して言語仕様にも盛り込み、作成者の意図のアピールを、利用者は意図を読み取るのを一助にする。
また、interfaceはその名の通り、クラスや機能・システム間の インターフェース にも利用される。直接機能クラス同士が相互的な依存関係を持つのではなく、interfaceをクッションに挟んだりなどして、依存性を下げ、保守性を高めたりなど。例えば、SOAP (HTTP通信+ XMLデータによるRemote Procedure Call) は言語やOSを超えたプラット フォーム間での相互利用を目標としているため、 インターフェースの考えが需要になる。(ソースや実装ではなく仕様書を見て!って言っている感じ)
穿った見方をすれば
・interface ⇒ 理想 (宣言と定義の分離)
・class ⇒ 現実 (1枚岩)
コンパクトな実装であればその理想に則り、定義 ⇒ 実装とウォーター ホール的な実装ができる。しかし、interfaceでは現実的には用が足りなくなることも実際のところしばしばある。(結局処理が冗長になって、処理の方も共通化したくなったり、メンバが持てない制約など。)
そして理想を諦めたときabstract classに降格して、継承関係で表現したりすることになる。(abstract classの存在をすっかり忘れていた。)。ただ、classは多重継承が行えないので別の制約で結局頭を抱えることになる。。
(interfaceを前提に作った共通関数が利用できなくなったりなど)
まぁなので結局一長一短だなぁと言う感じ。
(参考)
・【Java】「interface」のメリットと使い所を考えてみた - Qiita
https://qiita.com/shutokawabata0723/items/facc5fb71ff86010034f
・関数プロトタイプ - Wikipedia
https://ja.wikipedia.org/wiki/%E9%96%A2%E6%95%B0%E3%83%97%E3%83%AD%E3%83%88%E3%82%BF%E3%82%A4%E3%83%97
ライブラリインタフェースの生成
関数プロトタイプをヘッダファイルに置くことで、ライブラリのインタフェースを指定できる。
そう言えばC言語などのネイティブ コードの時代は、実行ファイルをリバース アセンブルするなどで力技で解析しないと、実装からは読み取れなかった。昨今のJavaやC#はモジュールにメタ データが残ってたり、何ならJavaはソースごと同梱したりもすることあるから、この辺の仕様と実装を切り分けたい気持ちには、ジェネレーション ギャップがあるかもしれない。インタプリタも流行りだし。
(おまけ①) オーバーライドは (あまり) したくない!
複数のクラスで共通のメソッド、かつ異なる挙動を継承で表現する場合、Javaの場合は オーバーライドになってしまう。しかし
オーバーライドはしとうないんじゃ!
オーバーライドは処理の上書きになる。元のメソッドがどの様な実装されているかも分からないのに上書きをするのはちょっと怖い。特に他者が作ったクラスを継承してオーバーライドはなかなかに。。。
そのため、オーバーライドは破壊的な行為とも取れる。
(できるならどんなコードを書いていいわけではない。開発者が想定した利用に汲み取り、それに則って利用するのが重要)
勿論、オーバーライドをして利用すること前提としているものも普通に沢山ある(InputStream, OutputStreamなど、、、)。そう言った内容をコードでは定義・表現出来ないため、悪法とまではいかないまでも、非常に扱いはナイーブ。interfaceは元より別個の実装をさせるためのもののため、『クラスを継承してオーバーライド』と『interfaceの実装』は大きく性質や用途が異なる。
(おまけ②) interfaceは『呼び出し規則と分類化。役割の追加』。 継承は『機能の共通と不足機能の追加』。
原則オーバーライドを行わないと考えた場合。
クラスの継承の場合は、基本的に派生クラスは派生元と同じ振る舞いを見せる。そして足りない機能を派生先で付け足していく形になる。
一方、interfaceは『分類化』を行う側面が強い。呼び出し規則を共通化して、異なる実装がされていても、共通の手続きで機能を実行することができる。また、interfaceを追加・実装することで役割の追加する、とも取れる。
class UltraMiracleSuperHyperClass implement Runnable, Serializeable {
int sttingsValueA;
int sttingsValueB;
String sttingsValueC;
}
・役割① Runnable ⇒ スレッド化できる
・役割② Serializeable ⇒ シリアライズができる
(serializerに食わせれば簡単にXMLファイルや
BASE64文字列などへの変換・複合が可能)
適切に定義すれば、意味有るコードの一助になり保守性もあがる。
(おまけ③) 異なる実装でも共通interfaceで呼び出し処理の共通化
① interfaceを定義
/** 動ける. */
interface Moveable{
void move();
}
② classを実装
class Human implements Moveable {
voud move (/** 足を前にだして1歩前動く */);
}
class Cat extends Pet implements Moveable {
voud move (/** 片側の前後の足を前にだして1歩前動く */);
}
class Dog extends Pet implements Moveable {
voud move (/** 片側の前後の足を前にだして1歩前動く */);
}
class Crab extends Seafood implements Moveable {
voud move (/** 右に動く. */);
}
③ 実行関数
void main(String arg[]) {
moveAll(new Moveable[] {
new Human(),
new Cat(),
new Dog(),
new Crab()
});
}
void moveAll(Moveable[] moveables) {
for(int = 0; i < moveables.length; i++) {
// Moveable.move()を実行するだけ
// どっち方向にどう動くかは感知しない。
// あくまで抽象化された『動く』を行わせるだけ
moveables[i].move():
}
}
Human、Cat、Dog、Crabはお互い継承関係のない異なる系統のクラスではあるが、一様にMoveableを実装しているため、Moveable.move()を実装していることが保証されている。そのため、Moveableの配列にまとめたり、共通処理で実行ができる。