web-dev-qa-db-ja.com

クラスが関数を直接実装できるのに、なぜインターフェースを使用するのですか?

重複の可能性:
なぜインターフェースが役立つのですか?

ほとんどの教員と同様に、私のJava教員は、その実用的な使用を説明したり言及したりすることなく、インターフェースを導入しました。今、インターフェースには非常に特殊な用途があると思いますが、答えを見つけることができないようです。

私の質問は、クラスがインターフェイス内の関数を直接実装できることです。例えば:

interface IPerson{
    void jump(int); 
}

class Person{
int name;
    void jump(int height){
        //Do something here
    }
}

具体的な違いは何ですか

class Person implements IPerson{
    int name;
    void jump(int height){
        //Do something here
    }
}

?作る

44
SoWhat

Javaは型に関して細心の注意が必要です。

例を少し拡張して、クラスの名前をJumpableに変更してみましょう。

_interface Jumpable {
    void jump(int);
}

class Person extends Mammal implements Jumpable {
    //other stuff
    void jump(int howHigh) {
        //jump method
    }
}

class Dog extends Mammal implements Jumpable {
    //other stuff
    void jump(int howHigh) {
        //jump method
        //also make him bark when he jumps
    }
}

class Cat extends Mammal implements Jumpable {
    //other stuff
    void jump(int howHigh) {
        //jump method
        //make it stretch its legs as well
    }
}

class FlyingFish extends Fish implements Jumpable {
    //other stuff
    void jump(int howHigh) {
        //jump method
        //make it come out of water
    }
}

class Mantis extends Insect implements Jumpable {
    //other stuff
    void jump(int howHigh) {
        //jump method
        //make it come out of water
    }
}

class Ant extends Insect { //Cannot jump
    //other stuff
}

class Whale extends Mammal { //Cannot jump (hopefully)
    //other stuff
}
_

ここには、いくつかの階層に編成されたさまざまなクラスがあることに注意してください。それらのすべてがジャンプできるわけではなく、ジャンプ機能はどのカテゴリにも普遍的に適用できません。各親クラス(AnimalMammalInsectFish)。

ジャンプ大会を開催したいとしましょう。 インターフェイスなしの場合、次のようにする必要があります。

_void competition(
    Person[] pCompetitors,
    Dog[] dCompetitors,
    Cat[] cCompetitors,
    FlyingFish[] fCompetitors,
    Mantis[] mCompetitors
) {
    for(int i=0; i<pCompetitors.length; i++) {
        pCompetitors[i].jump((int)Math.Rand() * 10);
    }
    //Do the same for ALL the other arrays.
}
_

ここでは、ジャンプできるすべてのクラスを保持する「エンベロープクラス」がないため、個別に呼び出す必要があります。コンパイラはjump()を呼び出せないため、_Animal[]_または_Mammal[]_配列だけを使用することはできません。すべてのAnimals/Mammalsがjump()できるわけではありません。

また、これは拡張できなくなります。別の跳躍クラスを追加したいとしましょう。

_class Bob extends Animal { //Bob is NOT a human. Bob is something....else....
    void jump(int howHigh) {
        //...
    }
}
_

ここで、competition(.........)を変更して、_Bob[]_パラメータも受け入れるようにする必要があります。また、_competition[]_のすべてのインスタンスを変更して、独自の_Bob[]_ sを作成するか、空の_Bob[]_パラメータを渡す必要があります。そして、それは厄介になります。


インターフェイスを使用した場合、競合方法は次のようになります。

_void competition(Jumpable[] j) {
    for(int i=0; i<j.length; i++) {
        j[i].jump((int)Math.Rand() * 10);
    }
}
_

これは、面倒なしに拡張することもできます。

基本的に、インターフェイスは、オブジェクトの予想される動作をコンパイラーに知らせます-コンパイラーが想定できるメソッド/データが存在すると想定します。そうすれば、一般的なプログラムを書くことができます。

また、インターフェースを複数継承することもできますが、Javaではextendsを使用してそれを行うことはできません。

実際の例:最初に思い浮かぶのは、AWTのイベントハンドラーです。これらにより、methodをパラメーターとして渡し、それをイベントハンドラーにすることができます。これは、メソッドが存在することをコンパイラーが確信している場合にのみ機能するため、インターフェースを使用します。

17
Manishearth

それは犬から始まります。特にパグ。

A pug

パグにはさまざまな動作があります。

public class Pug
{
    private String name;

    public Pug(String n)
    {
        name = n;
    }

    public String getName()
    {
        return name;
    }

    public String bark()
    {
        return "Arf!";
    }

    public boolean hasCurlyTail()
    {
        return true;
    }
}

Labrador Retriever

そして、あなたには一連の行動をするラブラドールがいます。

public class Lab
{
    private String name;

    public Lab(String n)
    {
        name = n;
    }

    public String getName()
    {
        return name;
    }

    public String bark()
    {
        return "Woof!";
    }

    public boolean hasCurlyTail()
    {
        return false;
    }
}

私たちはいくつかのパグとラボを作ることができます:

Pug pug = new Pug("Spot");
Lab lab = new Lab("Fido");

そして、それらの動作を呼び出すことができます:

pug.bark()           -> "Arf!"
lab.bark()           -> "Woof!"
pug.hasCurlyTail()   -> true
lab.hasCurlyTail()   -> false
pug.getName()        -> "Spot"
lab.getName()        -> "Fido"

Two cute dogs in a cage

犬小屋を経営していて、飼っているすべての犬を追跡する必要があるとします。パグとラブラドールを別々の配列に保存する必要があります。

public class Kennel
{
    Pug[] pugs = new Pug[10];
    Lab[] labs = new Lab[10];

    public void addPug(Pug p)
    {
        ...
    }

    public void addLab(Lab l)
    {
        ...
    }

    public void printDogs()
    {
        // Display names of all the dogs
    }
}

しかし、これは明らかに最適ではありません。プードルも入れたい場合は、Kennel定義を変更してPoodlesの配列を追加する必要があります。実際、犬の種類ごとに個別の配列が必要です。

Insight:パグとラブラドール(およびプードル)はどちらも犬の一種であり、同じ動作セットを持っています。つまり、(この例の目的で)すべての犬は吠え、名前を付けることができ、巻き毛の尾があるかどうかに関係なく言うことができます。インターフェースを使用して、すべての犬ができることdoを定義できますが、特定のタイプの犬に任せて、これらの特定の動作を実装できます。インターフェースは「すべての犬ができることはここにある」と述べていますが、各動作がどのように行われるか方法は示していません。

public interface Dog
{
    public String bark();
    public String getName();
    public boolean hasCurlyTail();
}

次に、PugおよびLabクラスをimplementDogの動作に少し変更します。 Pugisa Dog and a Labisa Dog

public class Pug implements Dog
{
    // the rest is the same as before
}

public class Lab implements Dog
{
    // the rest is the same as before
}

以前と同じようにPugsとLabsをインスタンス化することもできますが、今はそれを行う新しい方法も取得しています。

Dog d1 = new Pug("Spot");
Dog d2 = new Lab("Fido");

これはd1Dogだけでなく、Pugでもあります。そしてd2Dog、具体的にはLabです。

ビヘイビアーを呼び出すことができ、ビヘイビアーは以前と同様に機能します。

d1.bark()           -> "Arf!"
d2.bark()           -> "Woof!"
d1.hasCurlyTail()   -> true
d2.hasCurlyTail()   -> false
d1.getName()        -> "Spot"
d2.getName()        -> "Fido"

ここですべての余分な作業が報われます。 Kennelクラスはずっと単純になります。 1つの配列と1つのaddDogメソッドのみが必要です。どちらもis犬であるオブジェクトで機能します。つまり、Dogインターフェースを実装するオブジェクトです。

public class Kennel 
{
    Dog[] dogs = new Dog[20];

    public void addDog(Dog d)
    {
        ...
    }

    public void printDogs()
    {
        // Display names of all the dogs
    }
 }

使用方法は次のとおりです。

 Kennel k = new Kennel();
 Dog d1 = new Pug("Spot");
 Dog d2 = new Lab("Fido");
 k.addDog(d1);
 k.addDog(d2);
 k.printDogs();

最後のステートメントは次のように表示されます。

 Spot
 Fido

インターフェイスを使用すると、インターフェイスを実装するすべてのクラスが共通に共有する一連の動作を指定できます。その結果、変数やコレクション(配列など)を定義できます。これらは、インターフェイスを実装するオブジェクトを保持するだけで、それらが保持する特定のオブジェクトの種類を事前に知る必要はありません。

98
Barry Brown

IPersonインターフェースを使用すると、複数の実装者(ManWomanEmployeeなど...)を使用できますが、インターフェースを介してそれらすべてを処理できます。他のクラスで。

したがって、別のクラスでは単に次のように記述します。

void myMethod(IPerson person, Integer howHigh)
{
   person.jump(howHigh);
}

実装者ごとに個別のメソッドを用意する必要はありません。

29
Oded

インターフェースの目的は、コードを再配置または再利用することではなく、さまざまなタイプで動作するいくつかのメソッドがこれらのタイプ(引数として渡される)を正確に同じ方法で使用できるようにすることです。共通の親クラスはありません。

インターフェイスはコントラクトを作成するために使用されます-特定のクラスに存在することが期待される機能。

例えば:

interface Transmittable {
     public byte[] toBytes();
}

class Person implements Transmittable {
     public byte[] toBytes() {
         return this.name.getBytes()
    }
}

class Animal implements Transmittable {
     public byte[] toBytes() {
            return this.typeOfAnimal.getBytes()
    }
}

class NetworkTransmitter {
     public void transmit(Transmittable object) {
          byte data[] = object.toBytes();
         //do something....
     } 
}

class TestExample {
    public static void main(String args[]) {
           NetworkTransmitter trobj = new NetworkTransmitter();
           trobj.transmit(new Person());
           trobj.transmit(new Animal());
   }
}

これは、1つのクラスのオブジェクトが親から同じ名前のメソッドを継承(またはオーバーライド)する継承とは異なります。インターフェイスを実装するクラスは、同じ親クラスの子孫である必要はありませんが、コントラクトが存在することを確認したい他のクラスは、インターフェイスを実装するすべてのクラスのオブジェクトで同じメソッドを呼び出すことができます。インターフェイスは、この契約が利用可能であることを保証します。

14
mwallace

私はすべての開発者があなたの混乱を理解できると思います-インターフェイスの使用に頭を悩ませようとすることは常にうまく説明されるとは限りません。現実世界のプロジェクトで開発者として働き始めたとき、私はインターフェースの使い方を本当に理解し始めました。

BlackWaspにすばらしい記事があり、見事な例でインターフェースの使用法を説明しています-読んで試してみてください。それにより、理解が深まりました。

http://www.blackwasp.co.uk/Interfaces.aspx

3
Dal

インターフェイスを使用すると、個々のクラスの実装方法に関係なく、さまざまなクラスを共通の型としてキャストできます。

ここにすばやく簡単な例があります:

class Puppet : IMovement { ... }
class Vehicle : IMovement { ... }
class Car : Vehicle { ... }
class Bicycle : Vehicle { ... }

class Person : IMovement { ... }
class Child : Person { ... }
class Adult : Person { ... }

例のように、各クラスは直接または間接的にIMovementインターフェースを実装します。ただし、注意すべき重要な点は、例のクラスはどれも同じ共通のインターフェース型としてキャストできるということです。

((IMovement)puppet).Move()
((IMovement)car).Move()
((IMovement)child).Move()

インターフェイスを使用すると、既存のインターフェイスを変更せずに、クラスに新しい動作を簡単に導入できます。したがって、インターフェイスは継承に依存しない多態性を可能にします。これは、完全に異なるタイプのオブジェクトを同様の方法で処理する場合に非常に役立ちます。

1
S.Robins

インターフェースを持つ非常に興味深いユースケースがあります。ここに一つあります:

私がOS監視システムを作成していて、特定のイベントの発生後に何をすべきかを教えてくれたとします。ディスク容量が90%を超えるか、CPUの使用率が非常に高い場合や、一部のユーザーがログインしている場合などです。引き続き監視していますが、クライアントコードが現在発生している機能を提供する責任があります。

私のコード(OS監視システムです)では、特定のメソッドが実装されたオブジェクトを提供することを期待しています。 void OnDiskUsageHigh()などのようなメソッドを言います。私のコードでは、ディスク領域が少なくなったときに、このメソッドを呼び出すだけです。

これはコールバックメカニズムであり、インターフェイスと、ユーザーとの間に定義された一連のポリシーがないと、私のコードは一般的なクライアントのセットにサービスを提供できません。

実装する必要があるインターフェイスは次のとおりです(具体的なクラスを作成します)。

interface Callback { 
  void OnDiskUsageHigh();
  void OnCpuUsageHigh();
  ...
} 

そして、あなたは私のOSMonitoringクラスを、そのクラスがCallbackを実装するオブジェクトで初期化します

new OSMonitoringTool(new ConcreteCallbackClass());

ポリシー/契約ベースのプログラミングについてもう少し読む必要があります。

1
Fanatic23

この例では、あなたの混乱はひどい名前のクラスとインターフェースに帰着すると思います。

個人的には、インターフェイスの名前をIJumperにすると、混乱が少なくなると思います。これは、(Personグループよりも大きい個別のグループとして)ジャンプを許可するインターフェースです。

同じアナロジーを使い続けたい場合。 Personの名前をEarthPersonに変更します。次に、MarsPerson、JupitorPersonなどのIPersonインターフェイスを実装する代替クラスを作成できます。

これらの異なる人々はすべてジャンプすることができますが、ジャンプする方法は彼らがどのように進化したかに依存します。それでも、明示的な惑星の人ではなくIPersonで動作する汎用インターフェイスを提供したので、コードは変更なしで他の惑星の人でも機能します。

これにより、コードのテストも容易になります。

たとえば、Personオブジェクトの作成に非常にコストがかかる場合(Earth Wide DBで人物のすべての詳細を検索します)。テスト時には、実際のP​​ersonオブジェクトをデータとして使用したくない(作成にしばらく時間がかかる)ため、時間の経過とともに変化してテストが失敗する可能性があります。しかし、TestPerson(これはIPersonインターフェイスを実装する)オブジェクト(Personのモック)を作成し、それをPersonを受け入れるすべてのインターフェイスに渡して、それらが正しいことを行うことを確認できます。

1
Martin York

他のコメントに加えて、インターフェイスを使用すると、モック/スタブクラスのインターフェイスの重要な部分を明確に定義できるため、テストが簡単になります。 TDDはあらゆる場所でインターフェースを実行する必要があります。

0
anon

その振る舞いを提供するクラスを複数持つことができるようにしたいのでなければ、そうする必要はありません。

良い例は、SQLデータベースへの接続を表すJDBC Connectionインターフェイスです。プログラマーはSQLコマンドを送信して結果を返すことができます。基礎となるドライバーがデータベースと通信する方法は気にしませんが、実装がConnectionインターフェースを実装することを気にするので、DriverManagerが選択した任意の接続を使用できます。

0
user1249