前言
抽象类提供了一种更加方便的代码复用形式,为继承的子类提供了一组共享的属性和方法(可以不具体实现)。 混入是为解决Dart中只支持单继承而带来的局限性而引入的一种轻量级多重继承形式。抽象类与混入有许多相似的地方,因此这里将二者放在一起进行介绍。接下来,我们一起去了解一下什么是抽象类与混入。
一、抽象类
抽象类是类之上的抽象,其为类定义提供了一个可修改的模版或契约。
1.1、抽象类概念
抽象类是不能被实例化的类。其具有以下特点:
- 不能被实例化:抽象类不能被实例化为对象。
- 没有构造函数:抽象类不能被实例化为对象,也就不需要构造函数。
- 支持抽象方法:抽象类支持定义抽象的方法,即方法不用具体的实现细节,而强制由子类实现。
- 支持具体方法:抽象类和其它类一样也可提供具体方法。
1.2、抽象类的定义
抽象类使用 abstract 关键字定义。
由abstract关键字 + class关键字 + 抽象类类名 + 大括号({})组成。
示例:
/// 定义一个Animal的抽象类
abstract class Animal{
String name; // 抽象类的属性
Animal(this.name); // 为子类提供的构造函数
void introduce(){ // 具体方法,实现函数的具体细节
print('我是${this.name}');
}
void skill(); // 抽象方法,不实现函数的具体细节,强制其子类实现。
}
如果其子类不实现抽象方法则会出现如下图的错误。
实现抽象方法后:
/// 定义一个Animal的抽象类
abstract class Animal{
String name; // 抽象类的属性
Animal(this.name); // 为子类提供的构造函数
void introduce(){ // 具体方法,实现函数的具体细节
print('我是${this.name}');
}
void skill(); // 抽象方法,不实现函数的具体细节,强制其子类实现。
}
class Bird extends Animal{
Bird(super.name);
void skill(){
print('${this.name}会飞!');
}
}
void main() {
Bird parrot = Bird('鹦鹉');
parrot.introduce(); // 输出:我是鹦鹉
parrot.skill(); // 输出:鹦鹉会飞!
}
1.3、抽象类的优势
- 强制性:子类必须实现抽象类的抽象方法。
- 一致性:子类必须继承抽象类的属性和方法,确保了子类具有相同的结构。
- 复用性:抽象类为子类提供了属性和方法(和普通类一样提高了代码复用)。
二、混入类(Mixin)
混入(Mixin)是Dart提供的一种轻量级多重继承形式,其弥补了Dart单继承带来的缺陷。你可能会有一个疑问,Dart为什么不直接支持多继承呢?这不是更快捷吗?答案是多继承会带来一个难以抉择的局面,即菱形问题。下面我们先一起来看看什么是菱形问题。
2.1、菱形问题
菱形问题是当一个类从两个或多个基类派生,而这些基类又共同继承自同一个祖先类时,可能会出现方法或属性的二义性。
菱形问题又称为钻石问题,它是多继承带来的二义性问题。
注:称其为菱形问题是因其形状如菱形,故称为菱形问题,当出现复杂继承关系时就会出现其形状状如钻石。
如图所示,菱形问题即当一个子类继承多个父类的方法(或属性),而继承的这些方法(或属性)又同时继承自同一个父类。当这个子类去继承这两个父类的相同方法(或属性)时,会出现这个子类不知道选择那个父类的方法(或属性)去继承。
出现了问题,当然就要解决问题,不同的编程语言提出了不同的解决方法,Java中通过接口来实现类似于多继承的效果,避免了直接多继承带来的菱形问题。而在Dart中则是通过 Mixin(混入)来实现类似的效果。
2.2、混入的概念
混入(Mixin)Dart中解决菱形问题的一种方案,通过混入可以组合不同的类的功能到一个类中,而不需要复杂的类继承结构。其具有如下优势:
- 解决二义性:满足多继承的业务需求的同时,而不出现菱形问题。
- 简洁性:避免了传统多重继承带来的复杂性。
- 灵活性:可以在不改变类层次结构的同时轻松组合多种功能。
- 复用性:可以将常用的属性和方法封装在Mixin中,在需要时进行引入。
2.3、混入类的定义
混入(Mixin)使用 mixin 关键字定义。
由 mixin 关键字 + 类名 + 大括号({})组成。
注意:混入没有构造函数
示例:
/// 使用关键字mixin定义一个混入类Fly
mixin Fly{
void canfly(){
print('会飞!');
}
}
2.4、混入的使用
混入(Mixin)通过 with 关键字来使用。
示例: 通过混入实现单个功能的组合。
/// 使用关键字mixin定义一个混入类Fly
mixin Fly{
void canFly(){
print('我会飞!');
}
}
// 使用 Mixin 的类
class Bird with Fly{
String name;
Bird(this.name);
void introduce(){
print('我是${this.name}!');
}
}
void main() {
Bird parrot = Bird('鹦鹉');
parrot.introduce(); // 输出:我是鹦鹉!
parrot.canFly(); // 输出:我会飞!
}
示例: 通过混入实现多个功能的组合。
/// 定义混入类Fly、Roar
mixin Fly{
void canFly(){
print('我会飞!');
}
}
mixin Roar{
void canRoar(){
print('我会叫!');
}
}
// 使用 Mixin 的类
class Bird with Fly, Roar{
String name;
Bird(this.name);
void introduce(){
print('我是${this.name}!');
}
}
void main() {
Bird parrot = Bird('鹦鹉');
parrot.introduce(); // 输出:我是鹦鹉!
parrot.canFly(); // 输出:我会飞!
parrot.canRoar(); // 输出:我会叫!
}
2.5、混入的约束
混入(Mixin)可以约束哪些类可以进行混入。也就是说只有满足约束的类才可以使用with关键字进行使用。
混入约束条件使用 on 关键字定义。
示例:
/// 定义Animal类
class Animal{
String name;
Animal(this.name);
}
/// 定义Mixin类Fly并限定只能Animal派生类使用
mixin Fly on Animal {
void canFly(){
print('我会飞!');
}
}
mixin Roar{
void canRoar(){
print('我会叫!');
}
}
// 使用 Mixin 的类
class Bird extends Animal with Fly, Roar{
Bird(super.name);
void introduce(){
print('我是${this.name}!');
}
}
void main() {
Bird parrot = Bird('鹦鹉');
parrot.introduce(); // 输出:我是鹦鹉!
parrot.canFly(); // 输出:我会飞!
parrot.canRoar(); // 输出:我会叫!
}
如果不是Animal子类则会出现下图中的错误(编译器报错)。
2.6、混入约束的执行顺序
混入的约束具有执行顺序,越靠近 with 关键字的先进行混入。
示例:
mixin A {
void detailA() {
print('我是Mixin: A 的方法');
}
}
mixin B {
void detailB() {
print('我是Mixin: B 的方法');
}
}
// 通过on关键字约束,若要混入C必须是先混入A,B
mixin C on A, B {
void detailC() {
print('我是Mixin: C 的方法');
super.detailA();
super.detailB();
}
}
/// 定义MyClass 类
class MyClass with A, B, C { // 先混入A,B在混入C
void myMethod() {
detailA();
detailB();
detailC();
}
}
void main() {
MyClass myClass = MyClass();
myClass.myMethod();
}
// 输出:
我是Mixin: A 的方法
我是Mixin: B 的方法 // 混入 A、B的结果
我是Mixin: C 的方法 // 本行及下面的为混入 C 的结果
我是Mixin: A 的方法
我是Mixin: B 的方法
若定义MyClass类,混入A,B,C时不先混入A,B则会出现下图所示错误。
三、总结
本小节介绍了Dart中的抽象类、混入,其中在抽象类部分介绍了抽象类的一些优势及定义。在混入部分,首先介绍了多继承的二义性问题,其次为解决此问题接着介绍了混入的概念,最后介绍了Dart中混入的定义及混入的约束。