1. 序章

Abstraction は、オブジェクト指向プログラミングの主要な機能の1つです。 それは私たちを可能にしますより単純なインターフェースを介して機能を提供するだけで、実装の複雑さを隠すことができます。 Javaでは、次のいずれかを使用して抽象化を実現します。 インターフェースまたは抽象クラス

この記事では、アプリケーションの設計時にインターフェイスを使用する場合と抽象クラスを使用する場合について説明します。 また、それらの間の主な違いと、私たちが達成しようとしていることに基づいて選択するもの。

2. クラス対。 インターフェース

まず、通常の具象クラスとの違いを見てみましょう。 インターフェイス。

クラスは、オブジェクト作成の青写真として機能するユーザー定義の型です。 オブジェクトの状態と動作をそれぞれ表すプロパティとメソッドを持つことができます。

インターフェイスも、構文的にクラスに似たユーザー定義型です。 インターフェイス実装クラスによってオーバーライドされるフィールド定数とメソッドシグネチャのコレクションを持つことができます。

これらに加えて、 Java 8の新機能は、下位互換性をサポートするために、インターフェースで静的メソッドとデフォルトメソッドをサポートします。 インターフェイスのメソッドは、staticまたはdefaultでなく、すべて public である場合、暗黙的に抽象化されます。

ただし、Java 9以降では、プライベートメソッドをインターフェイスに追加することもできます。

3. インターフェイスと 抽象クラス

抽象クラスは、abstractキーワードを使用して宣言されるクラスに他なりません。 また、 abstract キーワード(abstractメソッド)を使用してメソッドシグネチャを宣言し、そのサブクラスに宣言されたすべてのメソッドを実装するように強制することもできます。 クラスに抽象メソッドがある場合、クラス自体は抽象でなければなりません。

抽象クラスにはフィールドとメソッドの修飾子に関する制限はありませんが、インターフェースでは、デフォルトですべてがパブリックになっています。 抽象クラスにインスタンスブロックと静的初期化ブロックを含めることはできますが、インターフェイスにそれらを含めることはできません。 抽象クラスには、子オブジェクトのインスタンス化中に実行されるコンストラクターが含まれる場合もあります。

Java 8では、機能インターフェースが導入されました。これは、宣言された抽象メソッドを1つだけ制限するインターフェースです。 静的メソッドとデフォルトメソッド以外の単一の抽象メソッドを持つインターフェイスは、機能インターフェイスと見なされます。 この機能を使用して、宣言する抽象メソッドの数を制限できます。 抽象クラスでは、抽象メソッド宣言の数にこの制限を設けることはできません。

抽象クラスは、いくつかの点でインターフェースに類似しています。

  • どちらもインスタンス化できません。つまり、ステートメント new TypeName()を直接使用してオブジェクトをインスタンス化することはできません。 前述のステートメントを使用した場合、匿名クラスを使用してすべてのメソッドをオーバーライドする必要があります
  • どちらにも、実装の有無にかかわらず、宣言および定義された一連のメソッドが含まれている場合があります。 つまり、インターフェイスの静的メソッドとデフォルトメソッド(定義済み)、抽象クラスのインスタンスメソッド(定義済み)、両方の抽象メソッド(宣言済み)

4. インターフェイスを使用する場合

インターフェイスを使用する必要がある場合のいくつかのシナリオを見てみましょう。

  • 多重継承を使用して問題を解決する必要があり、異なるクラス階層で構成されている場合
  • 無関係なクラスがインターフェイスを実装する場合。 たとえば、 Compareable は、2つのオブジェクトを比較するためにオーバーライドできる compareTo()メソッドを提供します
  • アプリケーションの機能をコントラクトとして定義する必要があるが、誰がその動作を実装するかについては気にしない場合。 つまり、サードパーティベンダーはそれを完全に実装する必要があります

問題が「Aは[これを行う]ことができる」というステートメントを作成する場合は、インターフェイスの使用を検討してください。 たとえば、「Clonableはオブジェクトのクローンを作成できます」、「Drawableは図形を描画できます」などです。

インターフェイスを利用する例を考えてみましょう。

public interface Sender {
    void send(File fileToBeSent);
}
public class ImageSender implements Sender {
    @Override
    public void send(File fileToBeSent) {
        // image sending implementation code.
    }
}

ここで、 Sender は、メソッド send()のインターフェイスです。 そのため、「送信者はファイルを送信できる」というインターフェースとして実装しました。 ImageSender は、ターゲットに画像を送信するためのインターフェイスを実装します。 さらに、上記のインターフェイスを使用して VideoSender DocumentSender を実装し、さまざまなジョブを実行できます。

上記のインターフェースとその実装されたクラスを利用する単体テストケースを考えてみましょう。

@Test
void givenImageUploaded_whenButtonClicked_thenSendImage() { 
 
    File imageFile = new File(IMAGE_FILE_PATH);
 
    Sender sender = new ImageSender();
    sender.send(imageFile);
}

5. 抽象クラスを使用する場合

ここで、抽象クラスを使用する必要がある場合のいくつかのシナリオを見てみましょう。

  • サブクラスがオーバーライドする共通の基本クラスメソッドを提供することにより、コードで継承の概念を使用しようとする場合(多くの関連するクラス間でコードを共有する)
  • 要件を指定し、実装の詳細を部分的にしか指定していない場合
  • 抽象クラスを拡張するクラスには、いくつかの一般的なフィールドまたはメソッドがあります(非公開の修飾子が必要です)。
  • オブジェクトの状態を変更するための非最終的または非静的なメソッドが必要な場合

問題が「AはBである」という証拠を作るときは、抽象クラスと継承の使用を検討してください。 たとえば、「犬は動物です」、「ランボルギーニは車です」などです。

抽象クラスを使用する例を見てみましょう。

public abstract class Vehicle {
    
    protected abstract void start();
    protected abstract void stop();
    protected abstract void drive();
    protected abstract void changeGear();
    protected abstract void reverse();
    
    // standard getters and setters
}
public class Car extends Vehicle {

    @Override
    protected void start() {
        // code implementation details on starting a car.
    }

    @Override
    protected void stop() {
        // code implementation details on stopping a car.
    }

    @Override
    protected void drive() {
        // code implementation details on start driving a car.
    }

    @Override
    protected void changeGear() {
        // code implementation details on changing the car gear.
    }

    @Override
    protected void reverse() {
        // code implementation details on reverse driving a car.
    }
}

上記のコードでは、 Vehicle クラスは、他の抽象メソッドとともに抽象として定義されています。 これは、実際の車両の一般的な操作を提供し、いくつかの一般的な機能も備えています。 Vehicleクラスを拡張するCarクラスは、車の実装の詳細を提供することにより、すべてのメソッドをオーバーライドします(「Caris aVehicle」)。

したがって、 Vehicle クラスを抽象として定義しました。このクラスでは、車やバスなどの個々の実際の車両で機能を実装できます。 たとえば、現実の世界では、車とバスの起動が同じになることは決してありません(それぞれに異なる実装の詳細が必要です)。

ここで、上記のコードを使用する簡単な単体テストについて考えてみましょう。

@Test
void givenVehicle_whenNeedToDrive_thenStart() {

    Vehicle car = new Car("BMW");

    car.start();
    car.drive();
    car.changeGear();
    car.stop();
}

6. 結論

この記事では、インターフェースと抽象クラスの概要、およびそれらの主な違いについて説明しました。 また、柔軟でクリーンなコードの記述を実現するために、それぞれを作業でいつ使用するかを検討しました。

この記事に記載されている例の完全なソースコードは、GitHubから入手できます。