在 Dart 中何时使用 mixins 以及何时使用接口?
Posted
技术标签:
【中文标题】在 Dart 中何时使用 mixins 以及何时使用接口?【英文标题】:When to use mixins and when to use interfaces in Dart? 【发布时间】:2018-02-04 16:17:03 【问题描述】:我对接口和抽象类的概念非常熟悉,但对mixins的概念不是很熟悉。
现在,在 Dart 中,每个类 A
都定义了一个隐式接口,可以通过使用 implements
关键字由另一个类 B
实现。没有明确的方式来声明接口,例如在 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 应用于超类,通过“正常”继承进行扩展,如下例所示:
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 到一个类,但是一个类可以从另一个类继承。
实现接口和从类继承之间也有明显的区别。实现接口的类需要强制实现接口定义的所有方法。
所以,总而言之,实现接口的概念更多是关于与实现接口的类建立契约,而mixin的概念(顾名思义)更多是关于重用代码(不重复继承层次结构)。
在设计软件时,是否有一些经验法则至少适用于特殊的循环模式,最好定义一个 mixin 并将其应用于超类,而不是让我们的类实现一个接口?我会欣赏在接口和 mixin 都可以使用的上下文中的设计决策的具体示例,但其中一个被用于另一个(出于某种原因)。
【问题讨论】:
看看这篇文章:google.com/url?sa=t&source=web&rct=j&url=https://… 【参考方案1】: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();
- type 是Map
,这是一个实现细节,它是一个HashMap
。
如果你混入一个类(或者更确切地说,从一个类派生的 mixin),那么你会在新的 mixin 类中获得该类的所有具体成员。 如果你实现了一个类(或者更确切地说,一个类的隐式接口),那么你根本就没有具体的成员,但抽象签名成为你接口的一部分。
有些类可以同时用作两者,但只有当一个类打算被用作混合时,你才应该将其用作混合(并记录在案)。类作者可以对类进行许多更改,这些更改会破坏它们作为 mixin 的使用。我们不想禁止任何此类更改,这对于非 mixin 类可能是完全合理的更改,因此使用非 mixin 类作为 mixin 是脆弱的,并且将来可能会损坏。
另一方面,打算用作 mixin 的类通常都是关于实现的,因此很可能也声明了类似的接口,这就是您应该在 implements 子句中使用的。
因此,如果您想实现一个列表,您可以实现 List
类并自己完成所有实现,或者混入 ListMixin
类以重用一些基本功能。你仍然可以写implements List<T>
,但是你可以通过继承ListMixin
来获得。
Mixins 不是一种获得经典意义上的多重继承的方法。 Mixins 是一种抽象和重用一系列操作和状态的方法。 它类似于通过扩展类获得的重用,但它与单继承兼容,因为它是线性的。 如果你有多重继承,你的类有两个(或更多)超类,你需要以某种方式处理它们之间的冲突,包括菱形继承。
Dart 中的 Mixin 通过创建一个新类来工作,该类将 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 Y implements I1 /* no members in I1 */
class $X2 extends $X1 implements I2 /* no members in I2 */
class $X3 extends $X2 implements I3 /* no members in I3 */
class X extends $X3 /* members of X */
你不会直接写,所以你也不应该用with
写它
【讨论】:
mixins 是否也有抽象方法?如果它们被用来重用代码,那么拥有未实现的方法对它们来说没有多大意义。 我注意到在 Dart 中,特别是在库collections
和 set.dart
部分中,它定义了一个 SetMixin
,它实际上声明了抽象(未实现)方法。从它的名字看,SetMixin
应该是一个 mixin,但它被用作超类,因为SetBase
实际上扩展了它。该文件中最奇怪的是BaseSet
基本上只做了一件有用的事情:扩展SetMixin
。为什么不简单地在BaseSet
中声明SetMixin
的方法呢?
SetBase
扩展 SetMixin
而不是仅仅因为 extends SetMixin
比 extends Object with SetMixin
短(并且因为它可以)而混合它。我们通常说你不能混入一个不适合它的类(命名或记录为可用作 mixin),因为这样做可能会在未来中断。这就是为什么我们有两个类,如 ListBase
和 ListMixin
,它们不一样(一个有一个 const 构造函数),我们有 SetBase
和 SetMixin
用于对称,即使 SetMixin
(当前)足够.
@lrn 如果我错了,请纠正我,但您的上一个代码 sn-p 中似乎有循环继承。【参考方案2】:
不要害怕mixin
,它来帮助你?
? 剧透:mixin
与动画毫无关系?,它只是另一个关键字,如@987654323@
但是mixin类似于:
快餐?/插件?/具有已实现方法和状态的接口,无需在任何我们需要的地方重新实现这些功能即可使用
当与StatefulWidget
的State
配对时,TickerProviderStateMixin
会创建ticker
,该ticker
会随着每个 AnimationController 所需的每一帧而滴答作响。当有状态的小部件处置时,它也会处置ticker。这就是我们在每个 AnimationController 中提供 this
作为 TickerProvider(vsync
) 的原因。
类似地,我们使用 ListMixin 来使用 List 的明显实现,这样我们就不必在每个 List 实现中实现明显的东西,例如 ElementList,NodeList,FileList,TouchList
等。
现在让我们尝试比较和收缩扩展、实现和混合
extends (inheritance)
=> 只能继承一个类及其公共/受保护成员和行为。
implements (contract)
=> 可以实现许多类,但我们必须重新定义每个行为。
with(mixin)
=> 可以混入很多类,我们可以重用它们的行为。
现在,如何使用 mixin:
任何类或抽象类都可以用作mixin。但是如果我们声明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 ✅
但是一个 mixin 不能使用另一个 mixin。
mixin C with B // Invalid ❌
【讨论】:
【参考方案3】: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>
来描述所需的位集实现接口,然后使用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;
【讨论】:
【参考方案4】:区别在于概念。如果你明白这一点,你就会以正确的方式使用它。
-
在 OOP 中,接口是强制派生类实现一组公共字段和方法的东西。
但与 C# 和 JAVA 等其他传统编程语言不同,Dart 没有显式接口类型。默认情况下,每个类都定义了自己的由公共字段和方法组成的接口。 因此,每个类都可以充当 Dart 中的接口。
implements 关键字是实现一个接口。此外,一个类可以实现多个接口。
-
在 OOP 中,继承意味着类之间的行为共享。我们不能与界面共享功能。所以,当我们实现一个类时,我们不能共享它的行为。
如果你想在这两个类之间共享行为,你应该使用 extends 关键字。
-
在 OOP 中,mixin 是一个包含供其他类使用的方法的类。与接口和继承方法不同,mixin 不必是其他类的父类。
因此,mixin 既不施加使用限制,也不强制类型限制。
您通常会将常用函数放在 mixin 中。通过使用 with 关键字来使用 mixin。
Taken from here
【讨论】:
【参考方案5】:mixin
与 swift 中的protocols
相同。在swift中,您可以定义具有默认实现的协议。 mixin 也提供了这种能力。
如果您想在遵守这些协议的同时为协议提供默认实现,或者想要遵守多个协议,请使用 mixin。否则使用接口。
【讨论】:
【参考方案6】:dart 接口,像另一种语言一样,为要实现的任何类定义一个协定,该协定需要实现他的公共属性和方法
mixin 它只是向你的类添加功能的另一种方式,因为在 dart 中不存在多重扩展。
【讨论】:
【参考方案7】:何时使用 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();
【讨论】:
以上是关于在 Dart 中何时使用 mixins 以及何时使用接口?的主要内容,如果未能解决你的问题,请参考以下文章