web-dev-qa-db-ja.com

ミックスインを使用する場合とDartでインターフェイスを使用する場合

私はインターフェイスと抽象クラスの概念に非常に精通していますが、mixinsの概念にはあまり精通していません。

現在、Dartでは、すべてのクラスAが暗黙的なインターフェイスを定義しており、Bキーワードを使用して別のクラスimplementsで実装できます。たとえばJavaのように、インターフェイスに未実装のメソッド(および最終的には静的変数)のみが含まれるような、インターフェイスを明示的に宣言する方法はありません。 Dartでは、インターフェイスはクラスによって定義されるため、インターフェイスAのメソッドは実際に既に実装されている場合がありますが、Bを実装するクラスはこれらの実装をオーバーライドする必要があります。

この状況は、次のコードから確認できます。

class A {
  void m() {
    print("method m");
  }
}

// LINTER ERROR: Missing concrete implementation of A.m
// Try implementing missing method or make B abstract.
class B implements A {
}

Dartでは、ミックスインも通常のクラス宣言によって定義されます...

...原則として、すべてのクラスは、そこから抽出できるミックスインを定義します。ただし、この提案では、コンストラクターが宣言されていないクラスからのみミックスインを抽出できます。この制限により、コンストラクターのパラメーターを継承チェーンに渡す必要があるために発生する問題を回避できます。

ミックスインは基本的に、実装されていないメソッドまたは実装されたメソッドの両方を定義できるクラスです。論理的に継承を使用する必要なく、別のクラスにメソッドを追加する方法です。 Dartでは、ミックスインはスーパークラスに適用され、次の例のように「通常の」継承を介して拡張されます。

class A {
  void m() {
    print("method m");
  }
}

class MyMixin {
  void f(){
    print("method f");
  }
}

class B extends A with MyMixin {
}

この場合、BAMyMixinの両方のメソッドを実装する必要がないことに注意してください。

少なくとも単一親継承のみをサポートする言語では、クラスへのmixinの適用とクラスからの継承には明確な違いがあります。その場合、多くのmixinをクラスが、クラスは別のクラスから継承することができます。

また、インターフェイスの実装とクラスからの継承には明確な違いがあります。インターフェイスを実装するクラスは、インターフェイスによって定義されたすべてのメソッドを強制的に実装する必要があります。

したがって、要約すると、インターフェイスを実装するという概念は、インターフェイスを実装するクラスとのコントラクトを確立することに関するものであり、(名前が示すように)ミックスインの概念は(継承階層を繰り返さずに)コードを再利用することに関するものです。

ミックスインを使用するタイミングとDartでインターフェイスを使用するタイミングクラスをインターフェースに実装するよりも、ミックスインを定義してスーパークラスに適用する方が良いソフトウェアを設計する際に、少なくとも特別な繰り返しパターンに関する経験則がありますか?インターフェイスとミックスインの両方を使用できるコンテキストでの設計決定の具体例に感謝しますが、一方が他方に対して使用されます(何らかの理由で)。

28
nbro

Mixinsは、クラスがどのように動作するかということであり、具体的な実装を継承および共有しています。インターフェイスは、クラスが何であるかについてのすべてであり、抽象署名であり、クラスが満たさなければならないことを約束します。 タイプです。

_class MyList<T> extends Something with ListMixin<T> ..._として実装されているクラスを使用します。このクラスはMyList<int> l = new MyList<int>();またはList<int> l = new MyList<int>()として使用できますが、ListMixin<int> l = new MyList<int>()を記述しないでください。それはListMixinを型として扱っているため、実際にはそうではないため、できません。 Map m = new HashMap();ではなくHashMap m = new HashMap();を常に記述する必要があるのと同じ理由です。typeMapであり、HashMapであることが実装の詳細です。

クラス(または、クラスから派生したミックスイン)をミックスすると、そのクラスのすべての具象メンバーが新しいミックスインクラスに追加されます。クラス(または、クラスの暗黙的なインターフェイス)を実装すると、具体的なメンバーはまったく得られませんが、抽象署名はインターフェイスの一部になります。

一部のクラスは両方として使用できますが、クラスをintendedがミックスインとして使用される(およびそのように文書化される)場合にのみ、クラスをミックスインとして使用する必要があります。クラス作成者がクラスに行うことができる多くの変更があり、ミックスインとしての使用を中断します。このような変更を禁止したくはありません。これは、非ミックスインクラスに対して完全に合理的な変更になる可能性があるため、ミックスインとして非ミックスインクラスを使用することは壊れやすく、将来的に壊れる可能性があります。

一方、ミックスインとして使用することを目的としたクラスは、通常、実装に関するものです。したがって、同様のインターフェイスも宣言されている可能性が高いため、implements句で使用する必要があります。

したがって、リストを実装する場合は、Listクラスを実装してすべての実装を自分で行うか、ListMixinクラスを組み合わせて基本機能を再利用できます。あなたはまだ_implements List<T>_を書くことができますが、それはListMixinからの継承によって得られます。

Mixinsは、古典的な意味で多重継承を取得する方法ではありません。 Mixinsは、一連の操作と状態を抽象化して再利用する方法です。クラスの拡張から得られる再利用に似ていますが、線形であるため単一継承と互換性があります。多重継承がある場合、クラスには2つ(またはそれ以上)のスーパークラスがあり、ダイヤモンド継承を含むそれらの間の競合を何らかの方法で処理する必要があります。

Dartのミックスインは、ミックスインの実装をスーパークラスの上に重ねて新しいクラスを作成する新しいクラスを作成することで機能します。これは、スーパークラスの「横」ではなく「上」なので、あいまいさはありません。ルックアップを解決する方法。

例:

_class Counter {
  int _counter = 0;
  int next() => ++_counter;
}
class Operation {
  void operate(int step) { doSomething(); }
}
class AutoStepOperation extends Operation with Counter {
  void operate([int step]) {
    super.operate(step ?? super.next());
  }
}
_

実際に起こるのは、新しいクラス「Operation with Counter」を作成することです。以下と同等です。

例:

_class Counter {
  int _counter = 0;
  int next() => ++_counter;
}
class Operation {
  void operate(int step) { doSomething(); }
}
class $OperationWithCounter = Operation with Counter;
class AutoStepOperation extends $OperationWithCounter {
  void operate([int step]) {
    super.operate(step ?? super.next());
  }
}
_

CounterからOperationへのmixinアプリケーションは新しいクラスを作成し、そのクラスはAutoStepOperationのスーパークラスチェーンに表示されます。

_class X extends Y with I1, I2, I3_を実行すると、4つのクラスが作成されます。 _class X extends Y implements I1, I2, I3_を実行するだけの場合、作成するクラスは1つだけです。 _I1_、_I2_、および_I3_のすべてが完全に空の抽象インターフェースであっても、withを使用してそれらを適用することは次と同等です:

_class $X1 extends X implements I1 {}
class $X2 extends $X1 implements I2 {}
class $X3 extends $X2 implements I3 {}
class X extends $X3 {}
_

それを直接書くことはないので、withを使って書くべきです。

16
lrn

JavaおよびC#は、インターフェイスを使用して、複数の実装継承の代わりに型の複数の継承を持ちます。複数の実装継承を持つ言語(Eiffel、C++、Dartなど)には、複雑さのトレードオフがあります) JavaおよびC#が回避することを選択したデザイナーに対処する必要があります。

ただし、複数の実装継承がある場合、インターフェイスはインスタンス変数を持たない抽象クラスの特別なケースになり、抽象メソッドとインターフェイス継承のみが継承と同じであるため、複数のインターフェイス継承を個別にサポートする必要はありません。そのようなクラスから。

例:

abstract class IntA {
  void alpha();
}

abstract class IntB {
  void beta();
}

class C extends IntA with IntB {
  void alpha() => print("alpha");
  void beta() => print("beta");
}

void main() {
  var c = new C();
  IntA a = c;
  IntB b = c;
  a.alpha();
  b.beta();
}

Dartには(mixinを介した)複数の実装継承があるため、個別の概念として複数のインターフェイス継承も、スタンドアロンエンティティとしてインターフェイスを個別に定義する方法も必要ありません。暗黙のインターフェイス(implements句を使用)は、あるクラスが少なくとも別のクラスと同じインターフェイスを実装していることを文書化または検証できるようにするために使用されます。たとえば、Int8ListList<int>を実装しますが、基になる実装は完全に異なります。

implementsを介して取得された継承/ミックスインおよび暗黙的なインターフェイスの使用は、一般的に直交しています。ほとんどの場合、それらを互いの代わりに使用するのではなく、一緒に使用します。たとえば、implements Set<int>を使用して、ビットセット実装の目的のインターフェイスを記述し、extendsおよび/またはwith句を使用して、そのための実際の実装を取り込むことができます。インタフェース。理由は、ビットセットがSet<int>と実際の実装を共有しないが、それらを互換的に使用できるようにすることです。

コレクションライブラリは、私たちにSetMixinミックスインを提供します。これは、いくつかの基本的なルーチンを自分で実装するだけで、それらに基づいてSet<T>実装の残りを提供します。

import "Dart:collection";

class BitSetImpl {
  void add(int e) { ...; }
  void remove(int e) { ...; }
  bool contains(int e) { ...; }
  int lookup(int e) { ...; }
  Iterator<int> get iterator { ...; }
  int get length { ...; }
}

class BitSet extends BitSetImpl with SetMixin<int> implements Set<int> {
  BitSet() { ...; }
  Set<int> toSet() { return this; }
}
5
Reimer Behrends

Dartインターフェースは、他の言語と同様に、実装するクラスにコントラクトを定義します。このコントラクトは、パブリックプロパティとメソッドを実装する必要があります

mixinは、クラスに機能を追加するもう1つの方法です。Dartにはマルチ拡張が存在しないためです。

1