Dart中的继承机制——分析extends、implements和mixin

·  阅读 1012
Dart中的继承机制——分析extends、implements和mixin

简介

Dart 是支持基于 mixin 继承机制的面向对象语言,所有对象都是一个类的实例,而除了 Null以外的所有的类都继承自 Object 类。本文通过一个汽车Car的例子讲解extends、implements、with以及扩展函数,以了解它们之间的特性和区别。

抽象类

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。 在Dart中,使用关键字 abstract 标识类可以让该类成为抽象类。抽象类常用于声明接口方法、有时也会有具体的方法实现。

///汽车抽象类
abstract class Car {
  int wheel = 4; //默认四个轮
  int horsepower; //马力
  ///设置马力
  void setHorsepower(int power) {
    horsepower = power;
  }
  ///驾驶
  void drive() {
    print('你可以把车开起来跑');
  }
  ///输出马力(抽象函数)
  void showHorsepower();
  ///门(抽象函数)
  void door();
}
复制代码

上面的代码定义了一个汽车的抽象类,他包括两个成员变量wheel、horsepower。以及四个方法。其中:showHorsepower door 为抽象函数,没有任何函数实现。drive setHorsepower 为普通的成员函数。

抽象函数有一下几个特性:

  1. 使用abstract表示,不能直接被实例化;
  2. 没有任何函数实现的函数即为抽象函数,无需使用abstract修饰;
  3. 抽象类可以没有抽象函数,但是有抽象函数的类一定为抽象类;
  4. 子类需要重写所有的抽象函数,但不要求必须重写父类的普通成员函数。

extends

与Java类似,Dart也是单继承,一个类只能有一个直接的父类,如果没有直接指明,则默认为Object。

范例和方法重写

class SportCar extends Car {
  final int defaultPower = 200;
  @override
  void door() {
    print('我是两门双座大后超有$wheel个轮子');
  }

  @override
  void showHorsepower() {
    if(horsepower == null) {
      super.setHorsepower(defaultPower);
    }
    print('我$horsepower匹马力');
  }
}
复制代码

上面的代码创建了一个跑车,并重写了door showHorsepower 函数。 其中showHorsepower函数首先判断horsepower是否为空,如果为空,则调用父类的showHorsepower函数设置马力值。而在door函数中,我们不难发现:子类是可以直接调用父类的私有变量的。 运行下面代码创建一个SportCar ,并执行相关方法:

var mx5 = SportCar();
mx5.showHorsepower();
mx5.door();
mx5.drive();
复制代码

输出结果为:

我200匹马力
我是两门双座大后超有4个轮子
你可以把车开起来跑
复制代码

可以看出,我们必须重写父类的抽象函数,并不强制要求我们重写成员函数。但是我们依旧可以重写这些成员函数。例如,我们要给跑车加个涡轮,以提升他的马力。我们重写setHorsepower 函数:

class SportCar extends Car {
  final int defaultPower = 200;
  @override
  void door() {
    print('我是两门双座大后超有$wheel个轮子');
  }

  @override
  void showHorsepower() {
    if(horsepower == null) {
      setHorsepower(defaultPower);
    }
    print('我$horsepower匹马力');
  }

  @override
  void setHorsepower(int power) {
    power =turbo(power);
    super.setHorsepower(power);
  }

  int turbo(int power){
    ///涡轮提升20%马力
    return power~/5 + power;
  }
}
复制代码

调整后的代码如上,运行效果结果如下:

我240匹马力
我是两门双座大后超有4个轮子
你可以把车开起来跑
复制代码

子类也可以重写父类的变量:

@override
int wheel = 8;

@override
void door() {
  print('我是两门双座大后超有${wheel}个轮子');
  print('我是两门双座大后超有${super.wheel}个轮子');
}
复制代码

输出结果如下:

我是两门双座大后超有8个轮子
我是两门双座大后超有4个轮子
复制代码

最后,加上完整的代码:

class SportCar extends Car {
  final int defaultPower = 200;
  @override
  int wheel = 8;

  @override
  void door() {
    print('我是两门双座大后超有${wheel}个轮子');
    print('我是两门双座大后超有${super.wheel}个轮子');
  }

  @override
  void showHorsepower() {
    if (horsepower == null) {
      setHorsepower(defaultPower);
    }
    print('我$horsepower匹马力');
  }

  @override
  void setHorsepower(int power) {
    power = turbo(power);
    super.setHorsepower(power);
  }

  int turbo(int power) {
    ///涡轮提升20%马力
    return power ~/ 5 + power;
  }
}
复制代码

总结

  1. 使用abstract表示,不能直接被实例化;
  2. 没有任何函数实现的函数即为抽象函数,无需使用abstract修饰;
  3. 抽象类可以没有抽象函数,但是有抽象函数的类一定为抽象类;
  4. 子类需要重写所有的抽象函数,但不要求必须重写父类的普通成员函数。
  5. 子类可以访问父类的成员变量和函数(私有的也可以访问),如果子类里复写了父类的函数或者成员变量,在子类里通过super.加函数名或者变量名访问父类的函数或者成员变量。子类和父类中的函数和变量是互不干扰的。

接口和implements

Dart中并没有interfaces关键字。当需要使用接口时,可以声明类来代替。因为在Dart中,使用的是一种隐式接口:每一个类都隐式地定义了一个接口并实现了该接口,这个接口包含所有这个类的实例成员以及这个类所实现的其它接口。如果想要创建一个A类支持调用B类的API且不想继承B类,则可以实现B类的接口。 例如,我们要造一辆坦克Tank,它有Car的马力和驾驶的,但是他不属于Car:

class Tank implements Car{
  @override
  int horsepower;

  @override
  int wheel = 5;

  @override
  void door() {
    print('我有$wheel五对负重轮');
  }

  @override
  void drive() {
    print('59下山了');
  }

  @override
  void setHorsepower(int power) {
    horsepower = power;
  }

  @override
  void showHorsepower() {
    print('我柴油机$horsepower匹马力');
  }

}
复制代码

运行结果如下:

我柴油机1600匹马力
我有5五对负重轮
59下山了
复制代码

注意上面的代码,当把一个类当做一个隐式的接口进行实现时,需要重写它所有的方法和成员变量

Dart同样支持多继承,如果需要实现多个类接口,可以使用逗号分割每个接口类:

class Tank implements Car, Cannon, Gun {
}
复制代码

完整的代码如下:

class Cannon {
  void fire() {
    print('开火!');
  }
}

abstract class Gun {
  void gun();
}

class Tank implements Car, Cannon, Gun {
  @override
  int horsepower;

  @override
  int wheel = 5;

  @override
  void door() {
    print('我有$wheel五对负重轮');
  }

  @override
  void drive() {
    print('59下山了');
  }

  @override
  void setHorsepower(int power) {
    horsepower = power;
  }

  @override
  void showHorsepower() {
    print('我柴油机$horsepower匹马力');
  }

  @override
  void fire() {
    print('开火!');
  }

  @override
  void gun() {
    print('This is machine gun!! OK,hahah');
  }
}
复制代码

Mixin和with

Dart增加了Mixin机制,也就是混入。mixins是一种实现多重继承的方式,通过它可以给现有的类添加特性,实现类似多继承的效果。例如我们燃油车有保养和加油的需求,电车有充电和保养的需求:

class Oil {
  void refuel() {
    print('去加油站加油了');
  }

  void maintain() {
    print('我需要更换机油三滤冷却液、检查轮胎蓄电池');
  }
}

mixin Electricity {
  void charge() {
    print('去充电桩充电了');
  }

  void maintain() {
    print('我需要检查轮胎电池');
  }
}
复制代码

如上文代码:Mixin可以是普通的class类,也可以使用mixin替换class去修饰。区别在于使用mixin修饰的类无法直接创建实例。

image.png

分别创建一个油车和电车:

class SedanOil extends Car with Oil {
  @override
  void door() {
    print('我是四门双座大后超');
  }

  @override
  void showHorsepower() {
    print('我$horsepower匹马力');
  }
}

class SedanElectricity extends Car with Electricity {
  @override
  void door() {
    print('我是五门五座瓦罐');
  }

  @override
  void showHorsepower() {
    print('我轮上马力$horsepower匹');
  }
}

main(){
print('--------油车:');
  var sedanOil = SedanOil()..horsepower =200;
  sedanOil.showHorsepower();
  sedanOil.maintain();
  sedanOil.refuel();

  print('--------电车:');
  var sedanElectricity = SedanElectricity()..horsepower = 300;
  sedanElectricity.showHorsepower();
  sedanElectricity.maintain();
  sedanElectricity.charge();
}
复制代码

输出结果如下:

--------油车:
我200匹马力
我需要更换机油三滤冷却液、检查轮胎蓄电池
去加油站加油了
--------电车:
我轮上马力300匹
我需要检查轮胎电池
去充电桩充电了
复制代码

如上面的代码:使用with关键字实现混入,多个被混入的类之间使用逗号隔开。 接下来,我们给油车增加油箱的相关操作:

class SedanOil extends Car with Oil,OilTank{
  @override
  void door() {
    print('我是四门双座大后超');
  }

  @override
  void showHorsepower() {
    print('我$horsepower匹马力');
  }

  @override
  void remove() {
    volume -=volumeSpare;
    print('去掉了$volumeSpare升的副油箱');
    volumeSpare = 0;
  }
}

abstract class OilTank{
  int volume = 68;
  int volumeSpare = 0;

  ///增加备用油箱
  void spare(int b){
    volumeSpare = b;
    volume+=b;
    print('增加了$volumeSpare升的副油箱');
  }

  ///去掉备用油箱
  void remove();
}
复制代码

混入一个抽象类OilTank ,该类允许我们增加一个副油箱。但是我们必须实现该抽象类的抽象方法remove

同理,对于电车,我们可以混入电池的相关操作:

abstract class ElectricityCar extends Car with Electricity implements Battery {
  @override
  void replenishCoolant() {
    print('加入冷却液');
  }
}

mixin BladeBattery on ElectricityCar{
  void safe(){
    print('刀片电池我更加安全了');
  }
}

class SedanElectricity extends ElectricityCar with BladeBattery{
  @override
  void door() {
    print('我是五门五座瓦罐');
  }

  @override
  void showHorsepower() {
    print('我轮上马力$horsepower匹');
  }

  @override
  int electricity;
}
复制代码

我们创建了一个SedanElectricity它继承自电车父类ElectricityCar,然后混入了刀片电池BladeBattery。注意BladeBattery的后面跟着关键字on,表明BladeBattery 的混入范围。这个关键字的意思是,想要混入BladeBattery 的类必须是其后面的ElectricityCar 的子类。对应的意思既是:刀片电池只有电车才能使用。

print('--------电车:');
var sedanElectricity = SedanElectricity()..horsepower = 300;
sedanElectricity.showHorsepower();
sedanElectricity.maintain();
sedanElectricity.charge();
sedanElectricity.replenishCoolant();
sedanElectricity.safe();
复制代码

运行上面的代码输出如下:

--------电车:
我轮上马力300匹
我需要检查轮胎电池
去充电桩充电了
加入冷却液
刀片电池我更加安全了
复制代码

此时,如果我们需要一辆插电混合动力的车:

class SedanDmi extends Car with Oil, Electricity {
  bool hasOil = false;

  @override
  void door() {
    print('我是四门五座车');
  }

  @override
  void showHorsepower() {
    print('我有$horsepower匹马力');
  }

  @override
  void maintain() {
    super.maintain();
  }

  @override
  void charge() {
    if (hasOil) {
      print('发动机给电池充电');
      hasOil = false;
    } else {
      print('去充电桩充电了或者去加油');
      hasOil = true;
    }
  }
}
复制代码

在一辆车中同事混入油和电,运行下面代码:

var dmi = SedanDmi()..horsepower = 300;
  dmi.showHorsepower();
  dmi.maintain();
  dmi.charge();
复制代码

输出结果为:

我有300匹马力
我需要检查轮胎电池
去充电桩充电了或者去加油
复制代码

注意: Oil和Electricity都需要保养,他们有同名方法maintain()。可以看到,在混入多个类时,如果存在同名函数,处于Mixin结尾的类将前面的同名方法都覆盖掉。

  1. 可以是普通类,使用mixin修饰后无法创建实例;
  2. 如果是抽象类,那么混入后需要实现所有的抽象函数;
  3. 可以使用关键字on来指定哪些类可以使用该 Mixin 类(当然,你也可以在超类中使用ElectricityCar实现这个mixin类,但这背离了设计的初中,这里不再展开说明)。
  4. 在混入多个类时,如果存在同名函数,处于Mixin结尾的类将前面的同名方法都覆盖掉。
  5. Mixin是一种在多个类层次结构中复用类代码的方法。

总结

个人的一点理解:

  • 1. extends继承规定了一个类时什么;
  • 2. implements实现接口表明了一个类有什么功能;
  • 3. with混入则是为类增加了某些功能,最重要的功能是复用代码。它与implements的显著区别是无需重写实现混入类的所有方法(抽象方法除外)

代码实例

分类:
Android
标签:
分类:
Android
标签:
收藏成功!
已添加到「」, 点击更改