2015-12-26 10 views
24

インターフェイスは柔軟性の点で優れています。しかし、インターフェイスが多数のクライアントによって使用される場合。古いメソッドをそのまま維持しながら新しいメソッドをインタフェースに追加すると、新しいメソッドがクライアントに存在しないため、すべてのクライアントのコードが破られます。以下に示すように:インターフェイスは時間とともに進化しますか?

いつか後で、このインターフェイスに別の方法を追加すると、すべてのクライアントのコードが破損します。私たちは、明示的にすべてのクライアントのコードで新しいメソッドを実装する必要があり、これを回避するために

public interface CustomInterface { 

    public void method1(); 

    public void method2(); 

} 

だから私は、次のようにインタフェースと、このシナリオを考える:追記

  1. インタフェースは石に彫刻のようなものです。彼らはめったに想定されず、変化することが予想されます。そしてもしそうなら、プログラマーが準備しなければならない膨大なコスト(コード全体を書き直す)が必要です。
  2. 上記の点を引き続き、時間のテストに耐えられるインターフェイスを作成することは可能ですか?
  3. このようなシナリオは、今後追加機能が期待されるインターフェイスでどのように処理されますか?これは、すべてのクライアントが拘束されている契約の変更を予期しています。

EDIT:Default実際には、多くの人がその答えで言及しているJavaインターフェイスには素晴らしい方法です。しかし、私の質問はコード設計の文脈の中でより多くのものでした。また、メソッドの実装をクライアント上で強制的に行うことは、インターフェイスの本質的な特徴です。しかし、インタフェースとクライアントの間のこの契約は機能が最終的に進化するにつれて脆弱に見えます。

+0

提案を読んでください:フレームワーク設計のガイドライン(.netですが、それはJavaプログラマーのためにも読んでおいてください)は、クラスまたはインタフェースの間のchosingについてのセクションがあります。他の答えで述べたように、クラスはインターフェイスよりも柔軟性があり、クライアントコードを壊さずに進化させるため、クラスの使用についても話しています。 –

答えて

21

この問題の解決策の1つは、Java 8ではインターフェイスのデフォルトメソッドの形で導入されました。既存のJava SEインターフェースに新しいメソッドを追加することができました。既存のJava SEインターフェースには、既存のコードをすべて破棄することなく新しいメソッドを追加することができました。 default void forEach(Consumer<? super T> action)default Spliterator<T> spliterator()及び -

は例えば、広く用いられているIterableインターフェースは、(それがCollectionインターフェイスのスーパーインターフェイスの)二つの新しいデフォルトの方法を添加しました。新しいインターフェイスは、新しいメソッドと一緒に以前のすべての方法を持っているし、あなたの必要な状況で、このインターフェイスを使用しますこれにより、上記のショーとして継承プロパティを使用することができ、デフォルトの方法以外

+0

大丈夫、私はそれを調べるでしょう。しかし、Java 8の設計の観点からは、インタフェースは不変であるはずですか?それはいったん顧客との契約が成立した後は変わるべきではない。 –

18
public interface CustomInterface { 
    public void method1(); 
} 

public interface CustomInterface2 extends CustomInterface { 
    public void meathod2(); 
} 

+1

しかし、それは私が探しているものではありません。上記のコードでは、クライアントコードが引き続き破損するためです。 –

+2

とはどのように壊れますか? –

+1

メソッド2が依然としてクライアントコードで実装されていないためです。 –

4

インターフェイスは開発者とクライアントの間の契約ですので、あなたは正しいのです。彼らは石で刻まれており、変更するべきではありません。したがって、インターフェイスは、クラスから絶対に必要とされる基本的な機能だけを公開(=要求)する必要があります。

たとえば、Listインターフェイスを使用します。 Javaのリストの多くは実装されていますが、その多くは時間の経過とともに進化しています(フット・アンダー・フード・アルゴリズムの改善、メモリー・ストレージの改善)が、リストの基本的な「概念」 - 項目の追加、項目の検索、 item - 変更しないでください。

あなたの疑問にお答えします:クラスが実装するインターフェイスを書く代わりに、抽象クラスを使用できます。インタフェースは、組み込みの機能を提供しないという意味で、基本的に純粋抽象クラスです。ただし、クライアントが実装する必要のない抽象クラスに新しい抽象メソッドを追加する()。です。

は、例えば、この抽象クラス(=インターフェイス)を取る:

abstract class BaseQueue { 
    abstract public Object pop(); 
    abstract public void push(Object o); 
    abstract public int length(); 
    public void clearEven() {}; 
} 

public class MyQueue extends BaseQueue { 
    @Override 
    public Object pop() { ... } 

    ... 
} 

ちょうどインターフェイスと同様に、BaseQueueを拡張するすべてのクラスは、契約抽象メソッドを実装するために結合されます。しかし、clearEven()メソッドは抽象メソッドではなく(既に空の実装が付属している)、クライアントはそれを実装したり、使用することさえ強制されません。

これは、契約上バインドされていないメソッドを作成するために、抽象クラスの能力をJavaで活用できることを意味します。あなたは抽象メソッドではないという条件で、他のメソッドを好きなだけ基本クラスに将来追加することができます。

+1

これは 'default'を使うのと同じですが、インターフェースの代わりに抽象クラスを使うことは、Javaが複数の継承を禁止するために頭痛の種を招きます。しかし、私はclearEven()の空の実装が実際にメソッドの実際の契約に違反している、具体的にはそれがどんなものであっても "クリア"するという理由で、これを打ち砕きました。 UnsupportedOperationExceptionをスローするより良い方法がありますが、それはまだ貧しい方法です。 BaseQueue < - ClearEvenableQueueの継承は、OOの考え方のクリーンなソリューションです。 –

+0

@SáT継承を単一の型に限定することは、OOプログラミングにおける非常に合理的かつ有用な設計原則です。だから私は、抽象クラスは多重継承をサポートしていないという理由だけで実装するのが難しいという意見に反対しがちです。あなたは、インタフェースと抽象クラスを比較しています。そのため、抽象クラスはインタフェースよりも柔軟性が低くなります。しかし、どちらもコード・アーキテクチャーの目的が非常に異なり、比較するべきではありません。私はあなたの2番目と3番目のポイントに同意しますが。 –

8

Java 8では、メソッドのデフォルト実装が導入されました。これらの実装はインタフェースに存在します。既定の実装を持つ新しいメソッドが既に多くのクラスによって実装されているインターフェイスに作成された場合、すべてのクラスを変更する必要はなく、新しく定義されたメソッドに対して異なる実装をデフォルトのもの。

ここで古いJavaバージョンはどうですか?ここでは、最初のインターフェースを拡張する別のインターフェースを使用できます。その後、新しく宣言されたメソッドを実装するクラスが変更され、新しいインターフェイスが実装されます。以下に示すように。

public interface IFirst { 
    void method1(); 
} 

public class ClassOne implements IFirst() { 
    public void method1(); 
} 

public class ClassTwo implements IFirst() { 
    public void method1(); 
} 

は今、私たちは、method2()が宣言したい、それだけでClassOneによって実装されなければなりません。

このアプローチはほとんどの場合は問題ありませんが、欠点もあります。たとえば、ClassTwoにはmethod3()(その1つだけ)が必要です。新しいインターフェースIThirdが必要になります。後でClassOneとClassTwoの両方で実装するmethod4()を追加したい場合は、とIThirdの両方を変更する必要があります(ただし、ClassThreeでもimplemented IFirstではなく変更する必要があります)。

プログラミングでは「魔法の弾丸」はほとんどありません。インタフェースの場合、変更されないのが最善です。これは実際の状況で常にそうであるとは限りません。そのため、インタフェースは単に「契約」(機能を持っている必要があります)を提供し、可能であれば抽象クラスを使用することが推奨されます。

5

今後のインターフェイスの変更によって、動作していたものが破損することはありません。そうした場合は、別のインターフェイスです。しかし、非推奨の例外を投げることは許容されていると言えるかもしれません。)

物事をインターフェースに追加するには、新しいインターフェースを派生させてくださいそれ。これにより、新しいビヘイビアーを実装するオブジェクトを古いものを想定したコードで使用し、ユーザーが適切に宣言したり、新しい機能にアクセスするために型キャストしたりすることができます。 instanceofテストが必要な場合があるので少し迷惑ですが、これは最も堅牢なアプローチであり、多くの業界標準で見られる方法です。

2

あなたの質問はデザインとテクニックの詳細なので、java8の回答は少し誤解を招くものです。この問題はjava8よりずっと前から知られていたので、いくつかの解決策があります。

まず、問題を解決するための全く新しい方法はありません。インターフェースの進化に伴って生じる不自由さの大きさは、ライブラリーの使用方法とデザインがどのように熟考されているかによって異なります。あなたがインターフェイスを設計し、その中に必須方法を含めるのを忘れた場合

1)ませ技術は、助けません。設計をよりよく計画し、クライアントがどのようにインタフェースを使用するかを予測してください。
例:turnOn()メソッドを持っていますが、turnOff()メソッドが欠けているMachineインターフェイスを想像してください。 java8でデフォルトの空の実装を持つ新しいメソッドを導入すると、コンパイルエラーは防げますが、メソッドの呼び出しは効果がないため、実際には役に立ちません。インターフェイスにはフィールドと状態がないため、作業の実装を行うことは不可能なことがあります。

2)異なる実装には、通常、共通するものがあります。親クラスに共通のロジックを維持することを恐れないでください。この親クラスからライブラリクラスを継承します。これにより、ライブラリクライアントは親クラスから独自の実装を継承するようになります。これで、すべてを壊すことなくインターフェイスを少し変更することができます。
例:インタフェースにisTurnedOn()メソッドを含めることにしました。基本クラスでは、デフォルトのメソッド実装を記述することができます。親クラスから継承されなかったクラスは、独自のメソッド実装を提供する必要がありますが、メソッドは必須ではないため、簡単に実装できます。

3)機能をアップグレードするには、通常、インターフェイスを拡張する必要があります。ライブラリクライアントに、必要のない新しいメソッドを実装する必要はありません。
例:インタフェースにstayIdle()メソッドを追加することにしました。これは、ライブラリのクラスに対しては意味を持ちますが、カスタムクライアントクラスに対しては意味を持ちません。この機能は新しいものなので、新しいインターフェイスを作成してMachineに拡張し、必要なときに使用する方がよいでしょう。

関連する問題