Dart 之抽象类、混入

351 阅读3分钟

Dart之抽象类、混入.png

前言

抽象类提供了一种更加方便的代码复用形式,为继承的子类提供了一组共享的属性和方法(可以不具体实现)。 混入是为解决Dart中只支持单继承而带来的局限性而引入的一种轻量级多重继承形式。抽象类与混入有许多相似的地方,因此这里将二者放在一起进行介绍。接下来,我们一起去了解一下什么是抽象类与混入。

一、抽象类

抽象类是类之上的抽象,其为类定义提供了一个可修改的模版或契约。

1.1、抽象类概念

抽象类是不能被实例化的类。其具有以下特点:

  • 不能被实例化:抽象类不能被实例化为对象。
  • 没有构造函数:抽象类不能被实例化为对象,也就不需要构造函数。
  • 支持抽象方法:抽象类支持定义抽象的方法,即方法不用具体的实现细节,而强制由子类实现。
  • 支持具体方法:抽象类和其它类一样也可提供具体方法。

1.2、抽象类的定义

抽象类使用 abstract 关键字定义。

由abstract关键字 + class关键字 + 抽象类类名 + 大括号({})组成。

示例:

/// 定义一个Animal的抽象类
abstract class Animal{
    String name; //  抽象类的属性
    Animal(this.name); // 为子类提供的构造函数
    void introduce(){  // 具体方法,实现函数的具体细节
        print('我是${this.name}');
    }
    void skill(); // 抽象方法,不实现函数的具体细节,强制其子类实现。
}

如果其子类不实现抽象方法则会出现如下图的错误。

抽象类示例错误.png 实现抽象方法后:

/// 定义一个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、菱形问题

菱形问题是当一个类从两个或多个基类派生,而这些基类又共同继承自同一个祖先类时,可能会出现方法或属性的二义性。

菱形问题又称为钻石问题,它是多继承带来的二义性问题。

注:称其为菱形问题是因其形状如菱形,故称为菱形问题,当出现复杂继承关系时就会出现其形状状如钻石。

多继承二义性图.png 如图所示,菱形问题即当一个子类继承多个父类的方法(或属性),而继承的这些方法(或属性)又同时继承自同一个父类。当这个子类去继承这两个父类的相同方法(或属性)时,会出现这个子类不知道选择那个父类的方法(或属性)去继承。

出现了问题,当然就要解决问题,不同的编程语言提出了不同的解决方法,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子类则会出现下图中的错误(编译器报错)。

混入错误示例.png

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则会出现下图所示错误。

混入顺序错误示例.png

三、总结

本小节介绍了Dart中的抽象类、混入,其中在抽象类部分介绍了抽象类的一些优势及定义。在混入部分,首先介绍了多继承的二义性问题,其次为解决此问题接着介绍了混入的概念,最后介绍了Dart中混入的定义及混入的约束。