1.简介:
上一章已经讲过类相关的基础,但是在项目中我们会面对一个这样的问题:怎样对现有类进行扩展呢?怎么样的扩展是最优的呢?
Dart中类扩展相关包括抽象类、接口实现、继承、扩展方法、混入等,这篇文章就是对这些知识的讲解,当你读过这篇文章后,会有一个更加直观的了解和自己需要的答案。
请注意该篇文章中的方法是指实例方法。
2.抽象方法abstract methods和抽象类Abstract classes:
2.1抽象方法:
只有声明没有实现(方法的实现又称作方法体)的方法叫做抽象方法。
- 只有实例、getter和setter方法可以是抽象的;
- 抽象方法定义了接口,但将接口的实现留给其它类;
- 抽象方法只能存在于抽象类中;
- 要定义抽象方法,请使用分号
;而不是方法体。
abstract class Doer {
// Define instance variables and methods...
void doSomething(); // Define an abstract method.
}
class EffectiveDoer extends Doer {
void doSomething() {
// Provide an implementation, so the method is not abstract here...
}
}
2.2抽象类:
使用abstract class Type定义的类就是抽象类,抽象类通常用于定义接口。使用抽象类的注意点:
- 抽象类无法实例化,但是可以有构造函数;
- 抽象类可以有抽象方法,也可以有普通方法;
- 抽象类中可以有属性。
abstract class Animal {
Animal(this.name);
late String name;
void eat();
void printInfo() {
print("动物的名字是$name");
}
}
3.实现接口Implicit interfaces:
在Dart中,每个类都隐式地定义一个接口;该接口包含类中的所有实例成员(实例变量和实例方法)。如果一个类(A类)只是想要其它类(B类)中的实例成员,则不需要继承类,只需要实现class A implements B。
类实现接口注意点:
- 通过关键字
implements; - 抽象类实现接口无需重写;
- 普通类实现接口,必须重写接口中所有实例成员,不包含构造函数;
- 可以实现多个接口。
abstract class Animal {
Animal(this.name);
late String name;
void eat();
void printInfo() {
print("动物的名字是$name");
}
}
abstract class Bird {
late String name;
late String wing;
Bird(this.name, this.wing);
void fly();
@override
void printInfo() {
print("鸟的名字是$name,鸟有翅膀");
}
}
abstract class Some implements Animal, Bird {
}
class SomeAnimal implements Animal, Bird {
@override
late String name;
@override
late String wing;
@override
void eat() {
// TODO: implement eat
}
@override
void fly() {
// TODO: implement fly
}
@override
void printInfo() {
// TODO: implement printInfo
}
}
4.继承extends:
- 子类继承父类语法
class Subclass extends Superclass; - 非抽象子类必须重写父类的抽象实例方法,抽象子类不需要;
- 父类有自定义构造函数,子类的构造函数必须用
:重定向父类构造函数; - 父类属性不需要重写;
- 父类的非抽象实例方法根据需要判断是否重写;
- 继承只能单继承,不能同时继承多个类。
abstract class Animal {
Animal(this.name);
late String name;
void eat();
void printInfo() {
print("动物的名字是$name");
}
}
abstract class Bird extends Animal {
late String wing;
Bird(String name, this.wing) : super(name);
void fly();
@override
void printInfo() {
print("鸟的名字是$name,鸟有翅膀");
}
}
class Duck extends Bird {
Duck(String name, String wing) : super(name, wing);
@override
void eat() {
// TODO: implement eat
}
@override
void fly() {
// TODO: implement fly
}
}
5.扩展方法Extension methods:
扩展方法是在Dart 2.7中引入的,它是一种向现有库添加功能的方法。当使用别人的API或当实现一个广泛使用的库时,更改API通常是不切实际或不可能的。但是可以用extension添加一些功能。
5.1扩展方法的一些注意点:
- 扩展用
extension Name on Type语法,on表示对某个类或者库进行的扩展; - 文件的私有扩展可以省略扩展名,或者用
_开头的名称; - 扩展不仅可以扩展方法,还可以扩展setter、getter和操作符方法等;
- 扩展可以有静态字段和静态方法;
- 扩展有名称,这在出现API冲突时很有帮助;
- 动态类型
dynamic使用扩展方法会运行时异常; - 当使用扩展中的方法时,引入扩展的文件即可。
// 定义扩展
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
static int int34(String value) {
return value.parseInt();
}
// ···
}
// 引入扩展所在的文件
import 'string_apis.dart';
// ···
print('42'.padLeft(5)); // Use a String method.
print('42'.parseInt()); // Use an extension method.
5.2静态类型Static types和动态类型dynamic:
不能对动态类型的变量调用扩展方法。例如,下面的代码会导致运行时异常:
dynamic d = '2';
print(d.parseInt()); // Runtime exception: NoSuchMethodError
扩展方法可以使用Dart的类型推断。下面的代码很好,因为变量v被推断为具有String类型:
var v = '2';
print(v.parseInt()); // Output: 2
动态类型的变量不能工作的原因是扩展方法是根据接收方的静态类型解析的。因为扩展方法是静态解析的,所以它们和调用静态函数一样快。
有关静态类型和动态类型的更多信息,请参见Dart类型系统。
5.3API冲突:
如果扩展成员与接口或另一个扩展成员冲突,解决方法:
- 导入扩展文件时使用
show或hide来限制暴露的API解决; - 显示应用扩展,
ExtensionType(var).method解决; - 如果两个扩展的名称相同,在导入扩展文件时使用别名解决。
// 使用`show`或`hide`来限制暴露的API解决
// Defines the String extension method parseInt().
import 'string_apis.dart';
// Also defines parseInt(), but hiding NumberParsing2
// hides that extension method.
import 'string_apis_2.dart' hide NumberParsing2;
// ···
// Uses the parseInt() defined in 'string_apis.dart'.
print('42'.parseInt());
// 显示应用扩展,`ExtensionType(var).method`解决
// Both libraries define extensions on String that contain parseInt(),
// and the extensions have different names.
import 'string_apis.dart'; // Contains NumberParsing extension.
import 'string_apis_2.dart'; // Contains NumberParsing2 extension.
// ···
// print('42'.parseInt()); // Doesn't work.
print(NumberParsing('42').parseInt());
print(NumberParsing2('42').parseInt());
// Both libraries define extensions named NumberParsing
// that contain the extension method parseInt(). One NumberParsing
// extension (in 'string_apis_3.dart') also defines parseNum().
import 'string_apis.dart';
import 'string_apis_3.dart' as rad;
// ···
// print('42'.parseInt()); // Doesn't work.
// Use the ParseNumbers extension from string_apis.dart.
print(NumberParsing('42').parseInt());
// Use the ParseNumbers extension from string_apis_3.dart.
print(rad.NumberParsing('42').parseInt());
// Only string_apis_3.dart has parseNum().
print('42'.parseNum());
如示例所示,即使使用别名前缀导入,也可以隐式地调用扩展方法。您唯一需要使用该前缀的时候是为了避免显式调用扩展时的名称冲突。
5.4实现泛型扩展:
扩展可以具有泛型类型参数。例如,下面是一些代码,它扩展了内置List<T>类型,带有getter、操作符和方法:
extension MyFancyList<T> on List<T> {
int get doubleLength => length * 2;
List<T> operator -() => reversed.toList();
List<List<T>> split(int at) => [sublist(0, at), sublist(at)];
}
泛型参数T是基于调用List方法的静态类型进行绑定的。
6.混入mixins:
混入mixin是一种在多个类层次中重用类代码的方法。
使用混入:
- 使用
mixin Type创建一个混入类; - 混入类不能有构造函数;
- 使用混入用
class ClassName with MixinName1, MixinName2语法; - 可以用
mixin MixinName on ClassName语法限制可以使用该混入的类,受限制的混入只有限制的类及其子类可以使用该混入。
// 定义一个混入类
mixin Musical {
bool canPlayPiano = false;
bool canCompose = false;
bool canConduct = false;
void entertainMe() {
if (canPlayPiano) {
print('Playing piano');
} else if (canConduct) {
print('Waving hands');
} else {
print('Humming to self');
}
}
}
// 在类中使用混入类
class Musician extends Performer with Musical {
// ···
}
class Maestro extends Person
with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
// 限制混入使用的类
class Musician {
// ...
}
mixin MusicalPerformer on Musician {
// ...
}
class SingerDancer extends Musician with MusicalPerformer {
// ...
}
在上面的代码中,只有Musician类及其子类SingerDancer能使用mixin MusicalPerformer。因为SingerDancer是Musician的子类,SingerDancer可以混入MusicalPerformer。
7.总结:
- 抽象类无法实例化,可以作为类被继承
extends,可以作为接口被实现implements,常用于定义一系列标准化的实例方法和实例属性; - 实现
implements接口,只是实现类的实例成员(实例变量和实例方法),一个类可以实现多个接口; - 继承
extends,继承父类的实例成员和静态成员; - 扩展
extension不能扩展实例变量和构造函数,可以扩展实例方法和静态成员; - 混入
mixin无法定义构造函数,可以混入实例成员,无法混入静态成员;