简介
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
为普通的成员函数。
抽象函数有一下几个特性:
- 使用abstract表示,不能直接被实例化;
- 没有任何函数实现的函数即为抽象函数,无需使用abstract修饰;
- 抽象类可以没有抽象函数,但是有抽象函数的类一定为抽象类;
- 子类需要重写所有的抽象函数,但不要求必须重写父类的普通成员函数。
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;
}
}
复制代码
总结
- 使用abstract表示,不能直接被实例化;
- 没有任何函数实现的函数即为抽象函数,无需使用abstract修饰;
- 抽象类可以没有抽象函数,但是有抽象函数的类一定为抽象类;
- 子类需要重写所有的抽象函数,但不要求必须重写父类的普通成员函数。
- 子类可以访问父类的成员变量和函数(私有的也可以访问),如果子类里复写了父类的函数或者成员变量,在子类里通过
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修饰的类无法直接创建实例。
分别创建一个油车和电车:
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结尾的类将前面的同名方法都覆盖掉。
- 可以是普通类,使用mixin修饰后无法创建实例;
- 如果是抽象类,那么混入后需要实现所有的抽象函数;
- 可以使用关键字on来指定哪些类可以使用该 Mixin 类(当然,你也可以在超类中使用ElectricityCar实现这个mixin类,但这背离了设计的初中,这里不再展开说明)。
- 在混入多个类时,如果存在同名函数,处于Mixin结尾的类将前面的同名方法都覆盖掉。
- Mixin是一种在多个类层次结构中复用类代码的方法。
总结
个人的一点理解:
- 1. extends继承规定了一个类时什么;
- 2. implements实现接口表明了一个类有什么功能;
- 3. with混入则是为类增加了某些功能,最重要的功能是复用代码。它与implements的显著区别是无需重写实现混入类的所有方法(抽象方法除外)