我非常熟悉接口和抽象类的概念,但不熟悉 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中,mixin也是通过普通的类声明来定义的......
...原则上,每个类都定义了一个可以从中提取的mixin。但是,在此提议中,mixin只能从没有声明构造函数的类中提取。这种限制避免了由于需要将构造函数参数传递到继承链而引起的复杂性。
mixin基本上是一个可以定义未实现或已实现的方法的类。它是一种将方法添加到另一个类而无需逻辑上使用继承的方法。在Dart中,mixin应用于超级类,通过" normal"继承,如下例所示:
class A {
void m() {
print("method m");
}
}
class MyMixin {
void f(){
print("method f");
}
}
class B extends A with MyMixin {
}
在这种情况下,我们应该注意B
不必实施A
和MyMixin
的任何其他方法。
将mixin 应用于类并继承自类,至少在仅支持单父继承的语言中有明显的区别,因为在这种情况下,我们可以将很多mixins应用于一个类,但是一个类可以从另一个类继承。
在实现接口和继承类之间也有明显的区别。实现接口的类需要强制实现接口定义的所有方法。
因此,总而言之,实现接口的概念更多的是与实现接口的类建立契约,而mixins的概念(顾名思义)更多的是重用代码(不再重复继承)层次)。
何时使用mixin以及何时在Dart中使用接口?在设计软件时,是否有一些经验法则至少要有特殊的循环模式,最好定义一个mixin并将其应用于超类,而不是让我们的类实现一个接口?我希望在可以使用接口和mixin的上下文中设计决策的具体示例,但是一个用于另一个(由于某种原因)。
答案 0 :(得分:13)
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();
的原因相同 - 类型是Map
,这是一个实现细节,它是HashMap
。
如果你混合使用一个类(或者更确切地说,是一个派生自类的mixin),那么你将在新的mixin类中获得该类的所有具体成员。 如果你实现了一个类(或者更确切地说,是一个类的隐式接口),那么你根本就没有具体的成员,但是抽象签名成为你接口的一部分。
有些类可以作为两者使用,但是如果意图用作mixin(并且记录在案),你应该只使用一个类作为mixin。类作者可以对一个会破坏其作为混合使用的类进行许多更改。我们不希望禁止任何此类更改,这对于非mixin类来说可能是完全合理的更改,因此使用非mixin类作为mixin是脆弱的,并且可能在将来中断。
另一方面,一个打算用作mixin的类通常都是关于实现的,所以很可能会声明一个类似的接口,这就是你应该在implements子句中使用的。
因此,如果要实现列表,可以实现List
类并自己完成所有实现,也可以在ListMixin
类中混合使用以重用某些基本功能。你仍然可以写implements List<T>
,但是你可以通过继承ListMixin
来获得它。
Mixins不是一种在经典意义上获得多重继承的方法。 Mixins是一种抽象和重用一系列操作和状态的方法。 它类似于扩展类所获得的重用,但它与单继承兼容,因为它是线性的。 如果你有多个继承,你的类有两个(或更多)超类,你需要以某种方式处理它们之间的冲突,包括钻石继承。
Dart中的Mixins通过创建一个新类来实现,该类将mixin的实现层叠在一个超类之上以创建一个新类 - 它不是“在旁边”而是在“超级”的“顶部”,所以有没有关于如何解决查找的含糊不清。
示例:
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());
}
}
真正发生的是您创建了一个新类“使用计数器操作”。它相当于:
示例:
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
,那么你创建了四个类。如果您只是class X extends Y implements I1, I2, I3
,那么您只创建一个类。即使所有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
答案 1 :(得分:4)
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具有多个实现继承(通过mixins),因此它不需要多个接口继承作为单独的概念,也不需要将接口单独定义为独立实体。隐式接口(通过implements
子句)用于记录或验证一个类至少实现与另一个类相同的接口。例如,Int8List
实现List<int>
,尽管底层实现完全不同。
通过implements
获得的继承/ mixins和隐式接口的使用通常是正交的;你很可能会联合使用它们而不是代替彼此。例如,您可能希望使用implements Set<int>
来描述bitset实现的所需接口,然后使用extends
和/或with
子句来引入该接口的实际实现。原因是您的bitset不会与Set<int>
共享任何实际实现,但您仍希望能够互换使用它们。
集合库为我们提供了SetMixin
mixin,它只需要我们自己实现一些基本例程,并根据这些实现提供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; }
}
答案 2 :(得分:4)
mixin
,它在这里可以帮助?mixin
与Animation毫无关系?,它只是另一个关键字,例如class
但是mixin类似于:
快餐?/插件?/具有已经实现的方法和状态的接口,可以随时使用而无需在需要的地方重新实现这些功能
与StatefulWidget
的{{1}}配对时,State
创建TickerProviderStateMixin
,它会与每个AnimationController所需的每一帧打勾。有状态窗口小部件处理时,它也配置代码。这就是为什么我们在每个AnimationController中将ticker
作为TickerProvider(this
)的原因。
类似地,我们使用ListMixin使用明显的List实现,因此我们不必在每个vsync
等List实现中都实现明显的东西。
ElementList,NodeList,FileList,TouchList
=>只有一个类及其公共/受保护的成员和行为可以被继承。
extends (inheritance)
=>可以实现许多类,但是我们必须重新定义每种行为。
implements (contract)
=>可以混入许多类,我们可以重用它们的行为。
现在,如何使用mixin:
任何类或抽象类都可以用作mixin。但是,如果我们声明mixin,则无法像普通类或抽象类一样对其进行扩展。
with(mixin)
但是混入不能使用其他混入。
class A{} //Declaring class
mixin B{} //Declaring mixin
class C extends A{} // Valid ✅
class C implements A{} // Valid ✅
class C with A{} // Valid ✅
class C extends B{} // Invalid ❌
class C implements B{} // Valid ✅
答案 3 :(得分:1)
mixin
与protocols
迅速。您可以快速定义具有默认实现的协议。 mixin也提供此功能。
如果要在遵循这些协议的同时提供协议的默认实现,或者要遵循多种协议,请使用mixin。否则使用界面。
答案 4 :(得分:0)
dart接口与另一种语言一样,定义了要实现的任何类的契约,该契约是其必需的实现其公共属性和方法的
mixin只是向类添加功能的另一种方法,因为在dart中不存在多重扩展。
答案 5 :(得分:0)
区别在于概念。如果您了解这一点,将以正确的方式使用它。
但是与其他传统编程语言(例如C#和JAVA)不同,Dart没有显式的接口类型。默认情况下,每个类都定义自己的由公共字段和方法组成的接口。 因此,每个类都可以充当Dart中的接口。
implements 关键字用于实现接口。另外,一个类可以实现多个接口。
如果要在这两个类之间共享行为,则应使用 extends 关键字。
因此,mixin既没有施加使用限制,也没有强加类型限制。
您通常会将常见功能放入mixin中。通过使用 with 关键字来使用mixin。
答案 6 :(得分:0)
何时使用 mixin : 这样我们就不需要在两个抽象类中定义pushup()、squat()
mixin CommonExercise {
pushUp() => print("push up");
squat() => print("squat ");
}
abstract class Sports with CommonExercise {
StrengthImprovement();
// pushup(){} // don't need to define
// squat(){}
}
abstract class Bodybuilding with CommonExercise {
muscleSizeIncrease();
}