(この質問の目的のために、私が「インターフェース」と言うときつまり、言語構成体interface
であり、 Wordの他の意味、つまり、クラスがそれと通信して操作するためにクラスが外の世界に提供するパブリックメソッド)
疎結合は、オブジェクトを具象型ではなく抽象に依存させることで実現できます。
これにより、2つの主な理由で疎結合が可能になります。1-=抽象化は具象型よりも変更される可能性が低く、依存するコードが破損する可能性が低くなります。 2-抽象化に適合するため、実行時にさまざまな具象型を使用できます。新しい具象型も後で追加でき、既存の依存コードを変更する必要はありません。
たとえば、クラスCar
と2つのサブクラスVolvo
およびMazda
を考えてみます。
コードがCar
に依存している場合は、実行時にVolvo
またはMazda
を使用できます。また、後で、依存コードを変更する必要なく、追加のサブクラスを追加できます。
また、Car
-抽象化です-は、Volvo
またはMazda
よりも変更される可能性が低くなります。車はかなり以前から一般的に同じでしたが、ボルボとマツダは変更される可能性がはるかに高いです。つまり抽象化は具象型よりも安定しています。
これはすべて、疎結合とは何か、それが具体化ではなく抽象化に依存することによってどのように実現されるかを理解していることを示すためです。 (私が不正確なものを書いた場合は、そのように言ってください)。
理解できないのはこれです:
抽象化はスーパークラスまたはインターフェイスにすることができます。
もしそうなら、なぜ疎結合を可能にする能力でインターフェースが特に賞賛されているのですか?スーパークラスを使用することとの違いがわかりません。
私が目にする唯一の違いは次のとおりです。1-インターフェイスは単一継承によって制限されませんが、疎結合のトピックとはあまり関係がありません。 2-インターフェイスには実装ロジックがないため、より「抽象的」です。しかし、それでもなぜそれがそんなに大きな違いを生むのかわかりません。
インターフェースが疎結合を許可するのに優れていると言われているのに、単純なスーパークラスがそうでない理由を説明してください。
用語:言語構造interface
をinterfaceとして参照し、型またはオブジェクトのインターフェースをsurfaceとして参照します(より適切な用語がないため)。
疎結合は、オブジェクトを具象型ではなく抽象に依存させることで実現できます。
正しい。
これにより、2つの主な理由で疎結合が可能になります。 1 -抽象化は具象型よりも変更される可能性が低く、依存するコードが破損する可能性が低くなります。 2 -抽象化に適合するため、実行時にさまざまな具象型を使用できます。新しい具象型も後で追加でき、既存の依存コードを変更する必要はありません。
不正解です。現在の言語では、抽象化が変更されることは一般的に想定されていません(ただし、それを処理するための設計パターンはいくつかあります)。詳細を一般的なものから分離is抽象化。これは通常、いくつかの抽象層によって行われます。このレイヤーは、この抽象化に基づいて構築されたコードを壊すことなく、他の特定の仕様に変更できます。疎結合が実現されます。非OOPの例:sort
ルーチンは、バージョン1のQuicksortからバージョン2のTim Sortに変更される可能性があります。したがって、ソートされる結果のみに依存するコード(つまり、sort
抽象化に基づいて構築)は、実際のソート実装から切り離されます。
上記で表面と呼んだのは、抽象化の一般的な部分です。これはOOPで発生します。1つのオブジェクトが複数の抽象化をサポートする必要がある場合があります。Javaの_Java.util.LinkedList
_は、「順序付けされたインデックス可能なコレクション」に関するList
インターフェースの両方をサポートします」の抽象化であり、(大まかに言えば)「FIFO」の抽象化に関するQueue
インターフェースをサポートしています。
オブジェクトはどのようにして複数の抽象化をサポートできますか?
C++にはインターフェースはありませんが、複数の継承、仮想メソッド、および抽象クラスがあります。抽象化は、仮想メソッドを宣言するが定義しない抽象クラス(つまり、すぐにインスタンス化できないクラス)として定義できます。抽象化の詳細を実装するクラスは、その抽象クラスから継承し、必要な仮想メソッドを実装できます。
ここでの問題は、多重継承がdiamond problemにつながる可能性があることです。この場合、メソッド実装のためにクラスが検索される順序(MRO:メソッド解決順序)が「矛盾」につながる可能性があります。これには2つの応答があります。
正しい順序を定義し、合理的に線形化できない順序を拒否します。 C3 MRO はかなり賢明で、うまく機能します。それは1996年に発行されました。
簡単な方法で、全体にわたって多重継承を拒否します。
Javaは後者のオプションを採用し、単一の動作継承を選択しました。ただし、複数の抽象化をサポートするオブジェクトの機能が必要です。したがって、メソッド定義をサポートせず、宣言のみをサポートするインターフェースを使用する必要があります。
その結果、MROは明白になり(各スーパークラスを順番に見るだけです)、オブジェクトには任意の数の抽象化に対して複数のサーフェスを含めることができます。
多くの場合、動作の一部が表面の一部であるため、これはかなり不十分です。 Comparable
インターフェースを考えてみましょう:
_interface Comparable<T> {
public int cmp(T that);
public boolean lt(T that); // less than
public boolean le(T that); // less than or equal
public boolean eq(T that); // equal
public boolean ne(T that); // not equal
public boolean ge(T that); // greater than or equal
public boolean gt(T that); // greater than
}
_
これは非常にユーザーフレンドリー(多くの便利なメソッドを備えた素晴らしいAPI)ですが、実装するのは面倒です。インターフェイスにcmp
のみを含めて、その1つの必須メソッドに関して他のメソッドを自動的に実装したいとします。 Mixins ですが、より重要なのはトレイト[ 1 ]、[ 2 ]により、多重継承のトラップに陥らずにこの問題を解決します。
これは、特性が実際にMROに参加しないように特性構成を定義することによって行われます。代わりに、定義されたメソッドが実装クラスに組み込まれます。
Comparable
インターフェースは、Scala
_trait Comparable[T] {
def cmp(that: T): Int
def lt(that: T): Boolean = this.cmp(that) < 0
def le(that: T): Boolean = this.cmp(that) <= 0
...
}
_
クラスがその特性を使用すると、他のメソッドがクラス定義に追加されます。
_// "extends" isn't different from Java's "implements" in this case
case class Inty(val x: Int) extends Comparable[Inty] {
override def cmp(that: Inty) = this.x - that.x
// lt etc. get added automatically
}
_
したがって、Inty(4) cmp Inty(6)
は_-2
_となり、Inty(4) lt Inty(6)
はtrue
となります。
多くの言語は、特性をいくらかサポートしており、「メタオブジェクトプロトコル(MOP)」を持つ言語には、特性を追加できます。最近のJava 8の更新により、特性に類似したデフォルトのメソッドが追加されました(インターフェースのメソッドはフォールバック実装を持つことができるため、これらのメソッドを実装するクラスを実装するためのオプションです)。
残念ながら、特性はかなり最近の発明(2002)であり、したがって、より大きな主流言語ではかなりまれです。
私が理解していないのはこれです:
抽象化は、スーパークラスまたはインターフェイスにすることができます。
もしそうなら、なぜ疎結合を可能にするそれらの能力のためにインターフェースが特に賞賛されるのですか?スーパークラスを使用する場合との違いはわかりません。
まず、サブタイピングと抽象化は2つの異なるものです。サブタイピングとは、あるタイプの値を別のタイプの値に置き換えることができることを意味します。どちらのタイプも抽象である必要はありません。
さらに重要なことに、サブクラスはスーパークラスの実装の詳細に直接依存しています。これが、最も強力な種類の結合です。実際、基本クラスが継承を考慮して設計されていない場合でも、動作を変更しない基本クラスへの変更は、サブクラスを壊す可能性があり、先験的にを知る方法はありません。 破損が発生した場合。これは 壊れやすい基本クラスの問題 として知られています。
インターフェースを実装しても、動作を含まないインターフェース自体以外は何も結合されません。
子は親に依存するため、親クラスと子クラスの間には結合があります。
クラスAがあり、クラスBがそれから継承するとします。クラスAに入って変更すると、クラスBも変更されます。
インターフェースIがあり、クラスBがそれを実装するとします。インターフェイスIを変更すると、クラスBはそれを実装しなくなる可能性がありますが、クラスBは変更されません。