インタフェースはインスタンスを作らないため、どんだけ実装しても問題ないことが分かったと思います。
では、そもそもインタフェースは何のために使うのでしょうか。
「したい」と「する」を分ける
インタフェース最大の特徴は「したい」と「する」を分けられるということです。
メソッドを使う時には、まず「したい」があります。文字列を出力したい、計算をしたい、データベースにアクセスしたい――そういった「したい」がまず最初にあります。
その「したい」を元に実際にメソッドを呼び出すと、呼び出されたメソッドが処理を「する」ことになります。メソッドは、文字列を出力する、計算をする、データベースにアクセスする――そういった処理を実際に「する」わけです。
インタフェースを使うと、この2つを分けることができます。
「したい」をインタフェースに書き、「する」を実装クラスに書くことで、2つを分けることができます。
この例を実際に見てみましょう。
たとえば、プログラムを2つのグループ、AグループとBグループで作っているとします。
AグループにはAGroupClassクラスがあり、その中でBグループが作る予定の機能を使っています。ただ、まだ細かい所までは決まっていないため、Bグループがどういうクラスを作るかまでは分かりません。とりあえず「Aグループが必要な機能をBグループが作る」、ということだけは分かっています。
さて、このような時、AGroupClassクラスからBグループの機能を使う箇所は、どのようにすればいいのでしょうか。
このような場合、Bグループにして欲しいこと、つまり「したい」ことをインタフェースで書いて、それを呼び出します。
/**
* インタフェース。
* Bグループのクラスで作るはずの機能を呼び出すために
* Aグループ側で用意します。
*/
interface BGroupInterface
{
/**
* 抽象メソッド。
* 計算してもらいます。
*/
int calc( int lh, int rh );
}
/**
* Aグループのクラス。
*/
class AGroupClass
{
/**
* Bグループの処理を行うインタフェース。
*/
BGroupInterface bGroup;
/**
* 何か処理をするメソッド。
*/
void doLogic()
{
// 色々処理をしてー。
// Bグループの機能を呼び出す箇所。
int i = bGroup.calc( 100, 200 );
System.out.println( i );
// で、残りの処理~。
}
}
/**
* BGroupInterfaceインタフェースの実装クラスのダミー。
* Bグループが作るまでの仮クラス。
*/
class BGroupClassDummy implements BGroupInterface
{
/**
* 実装メソッド。
* とりあえず値を返します。
*/
public int calc( int lh, int rh )
{
return 300;
}
}
/**
* 実行用クラス。このクラスを実行してください。
*/
class AGroupClassRunner
{
public static void main( String[] args )
{
// AGroupClassクラスのインスタンスを1つ作ります。
AGroupClass ref = new AGroupClass();
// Bグループで作ってくれるはずクラスのダミーを作ります。
// 本番環境ではBグループのクラスを使いましょう。
ref.bGroup = new BGroupClassDummy();
// doLogic()メソッドを呼び出します。
ref.doLogic();
}
}
// AGroupClassRunner.java /** * インタフェース。 * Bグループのクラスで作るはずの機能を呼び出すために * Aグループ側で用意します。 */ interface BGroupInterface { /** * 抽象メソッド。 * 計算してもらいます。 */ int calc( int lh, int rh ); } /** * Aグループのクラス。 */ class AGroupClass { /** * Bグループの処理を行うインタフェース。 */ BGroupInterface bGroup; /** * 何か処理をするメソッド。 */ void doLogic() { // 色々処理をしてー。 // Bグループの機能を呼び出す箇所。 int i = bGroup.calc( 100, 200 ); System.out.println( i ); // で、残りの処理~。 } } /** * BGroupInterfaceインタフェースの実装クラスのダミー。 * Bグループが作るまでの仮クラス。 */ class BGroupClassDummy implements BGroupInterface { /** * 実装メソッド。 * とりあえず値を返します。 */ public int calc( int lh, int rh ) { return 300; } } /** * 実行用クラス。このクラスを実行してください。 */ class AGroupClassRunner { public static void main( String[] args ) { // AGroupClassクラスのインスタンスを1つ作ります。 AGroupClass ref = new AGroupClass(); // Bグループで作ってくれるはずクラスのダミーを作ります。 // 本番環境ではBグループのクラスを使いましょう。 ref.bGroup = new BGroupClassDummy(); // doLogic()メソッドを呼び出します。 ref.doLogic(); } }
AグループがAGroupClassクラスを作っています。
その中のdoLogic()メソッドで、Bグループの機能を使うことになっているとします。
* Aグループのクラス。
*/
class AGroupClass
{
/**
* 何か処理をするメソッド。
*/
void doLogic()
{
// 色々処理をしてー。
// Bグループの機能を呼び出す箇所。
// TODO:どうしよう! Bグループの計算機能を使いたい!
// で、残りの処理~。
}
}
/** * Aグループのクラス。 */ class AGroupClass { /** * 何か処理をするメソッド。 */ void doLogic() { // 色々処理をしてー。 // Bグループの機能を呼び出す箇所。 // TODO:どうしよう! Bグループの計算機能を使いたい! // で、残りの処理~。 } }
doLogic()メソッドで、Bグループが作る予定の「計算機能」を使う必要があります。
でも、Bグループはまだ作ってないので使えません。どんなクラスのどんなメソッドに入れるのかも決まっていません。
ただ、必要な計算機能をBグループが作る! ということだけは決まっています。
そんなときは、その計算機能を行うメソッドをインタフェースに書いちゃいます。
それがBGroupInterfaceインタフェースです。
* インタフェース。
* Bグループのクラスで作るはずの機能を呼び出すために
* Aグループ側で用意します。
*/
interface BGroupInterface
{
/**
* 抽象メソッド。
* 計算してもらいます。
*/
int calc( int lh, int rh );
}
/** * インタフェース。 * Bグループのクラスで作るはずの機能を呼び出すために * Aグループ側で用意します。 */ interface BGroupInterface { /** * 抽象メソッド。 * 計算してもらいます。 */ int calc( int lh, int rh ); }
BGroupInterfaceインタフェースを作り、その中にBグループの計算機能を使うためのcalc()メソッドという抽象メソッドを書きます。
このメソッドは「Aグループでしたいこと」です。
Aグループは、Bグループの計算機能を使いたい! それを、インタフェースに書いたわけです。
そして、このインタフェースをAGroupClassクラスで使います。
* Aグループのクラス。
*/
class AGroupClass
{
/**
* Bグループの処理を行うインタフェース。
*/
BGroupInterface bGroup;
/**
* 何か処理をするメソッド。
*/
void doLogic()
{
// 色々処理をしてー。
// Bグループの機能を呼び出す箇所。
int i = bGroup.calc( 100, 200 );
System.out.println( i );
// で、残りの処理~。
}
}
/** * Aグループのクラス。 */ class AGroupClass { /** * Bグループの処理を行うインタフェース。 */ BGroupInterface bGroup; /** * 何か処理をするメソッド。 */ void doLogic() { // 色々処理をしてー。 // Bグループの機能を呼び出す箇所。 int i = bGroup.calc( 100, 200 ); System.out.println( i ); // で、残りの処理~。 } }
BGroupInterfaceインタフェースの参照型変数bGroupフィールドを持ち、そのフィールドを通してcalc()メソッドを呼び出します。
つまり、このようにしてBグループの機能を使用しているわけです。
Aグループの「したい」を、インタフェースを使って実現したわけです。
と言っても、この段階だとその「したい」を「する」クラス、つまりBGroupInterfaceインタフェースの実装クラスがまだありません。Bグループがまだ作ってないから当然です。
じゃあ、ということでとりあえず結果だけ返すニセモノのクラスを作っちゃいます。
それがBGroupClassDummyクラスです。
* BGroupInterfaceインタフェースの実装クラスのダミー。
* Bグループが作るまでの仮クラス。
*/
class BGroupClassDummy implements BGroupInterface
{
/**
* 実装メソッド。
* とりあえず値を返します。
*/
public int calc( int lh, int rh )
{
return 300;
}
}
/** * BGroupInterfaceインタフェースの実装クラスのダミー。 * Bグループが作るまでの仮クラス。 */ class BGroupClassDummy implements BGroupInterface { /** * 実装メソッド。 * とりあえず値を返します。 */ public int calc( int lh, int rh ) { return 300; } }
BGroupClassDummyクラスはBGroupInterfaceインタフェースを実装し、calc()メソッドを実装しています。
でも、中身は単に値を返しているだけ。計算処理はしていません。まぁそれ書いちゃったらBグループの仕事を代わりにしちゃうことになるので、当然細かい処理は書きません。
でも、これで十分です。なぜかというと、これだけでテストができるからです。
つまり、このダミークラスを用意することで、AGroupClassクラスのテストができるわけです。
というわけで、テストしましょう。
AGroupClass ref = new AGroupClass();
// AGroupClassクラスのインスタンスを1つ作ります。 AGroupClass ref = new AGroupClass();
まずAGroupClassクラスのインスタンスを作ります。
この時点ではまだBGroupInterfaceインタフェースの実装クラスはセットしていません。
bGroupフィールドはBGroupInterfaceインタフェースの参照型変数です。
このインタフェースを通して、AGroupClassクラスの「したい」を実現します。
そこで、とりあえずテスト用ということで、ダミークラスを使います。
// 本番環境ではBグループのクラスを使いましょう。
ref.bGroup = new BGroupClassDummy();
// Bグループで作ってくれるはずクラスのダミーを作ります。 // 本番環境ではBグループのクラスを使いましょう。 ref.bGroup = new BGroupClassDummy();
BGroupClassDummyクラスのインスタンスを作って、それをbGroupフィールドにセットします。
この時、頭の中で「がちゃこん」という連結音が聞こえるとナイスです。
インタフェースにダミークラスを連結する――そんなイメージで、bGroupフィールドにBGroupClassDummyクラスのインスタンスの参照をセットします。
そうすると、インタフェースを通してBGroupClassDummyクラスのメソッドを呼び出すことができるようになります。
あとはdoLogic()メソッドを実行します。
ref.doLogic();
// doLogic()メソッドを呼び出します。 ref.doLogic();
doLogic()メソッドで「したい」ことは、BGroupClassDummyクラスのcalc()メソッドでとりあえず「する」ことで、doLogic()メソッドがとりあえず動きます。
こうすることでdoLogic()メソッドのテストが完了します。
こうして、Bグループがまだクラスを作っていない段階で、AグループのAGroupClassクラスが作れて、実行が可能になるわけです。
あとはBグループがクラスを作るのを待つだけです。
Bグループは必要な機能を持つクラスをまず作ります。
次に、AグループのBGroupInterfaceインタフェースでそのクラスを実装します。calc()メソッドを実装して、その中で計算処理を呼び出します。つまりBGroupClassDummyクラスの本物バージョンを作るわけです。
これで「する」ができました。
あとは「ref.bGroup = new そのクラス();」という感じにセットすれば、AグループのクラスとBグループのクラスが連結して動くわけです。
インタフェースはクラスを制限しない!
インタフェースを使う最大のメリットは「クラスを制限しない」ということです。
たとえば、今回の例でBGroupInterfaceインタフェースが抽象クラスだった場合、Bグループは「その抽象クラスのサブクラス」を用意する必要があります。
つまり抽象クラスを使っちゃうと「使えるのはそのサブクラスのみ」という制限ができちゃうわけです。
ところが、インタフェースならそんな制限ありません。
「10.5 インタフェースはクラスじゃない!」で説明したように、インタフェースは「クラスの木」とは関係ありません。どんなクラスにも自由にくっつけることができます。
また「10.3 インタフェースのキャストは自由!」で説明したように、インタフェースへのキャストも自由にできるので、どんなクラスもbGroupフィールドに渡せます。
ということは、BGroupInterfaceインタフェースの実装クラスはどんなクラスでも構わないということです。
BGroupInterfaceインタフェースは、Aグループ側の「したい」を書いただけです。
それを「どんなクラス」が「どんな形」で「する」のか、それはBグループの自由です。どのように実装するのかは、Bグループの好きにして構わないわけです。
必要なことは、Aグループの「したい」をちゃんと実現するということのみ。正確に言えば、Bグループが作らなくちゃいけない「計算機能」の仕様を満たすこと。それができていれば、どんなクラスが行おうが構わないわけです。
このようにインタフェースを使うことで、Aグループの「したい」とBグループの「する」を分けることができて、それぞれが自由に作り、あとでくっつけるということが可能になるわけです。