Dart之抽象类、混入

一、前言
在前面学习继承和多态时,我们知道子类可以继承父类,也可以重写父类方法。
但实际开发里还会遇到两个问题:
第一,有些父类只是用来规定能力,并不希望直接创建对象。比如“动物”这个概念可以作为抽象分类,但真正运行时通常是猫、狗、鸟这些具体对象。
第二,有些能力并不适合放进单一继承关系里。比如“会飞”“会游泳”“会记录日志”这些能力,可能很多不同类都需要,但它们之间未必有父子关系。
这就引出了 Dart 中两个重要概念:抽象类和混入。
二、一句话理解
抽象类用于定义一类对象应该具备的共同规则,混入用于把一组可复用能力灵活添加到类中。
简单说:
- 抽象类:规定“你应该有什么能力”。
- 抽象方法:只声明,不实现,让子类去完成。
- 混入:把一组能力插到类里复用。
- 继承:表达“是什么”。
- 混入:表达“能做什么”。
三、抽象类
3.1 什么是抽象类
抽象类使用 abstract 修饰。
它通常用于描述一类对象的共同规则,但不一定直接创建对象。
abstract class Animal {
void eat();
}
这里 Animal 是抽象类,eat() 是抽象方法。
抽象方法没有方法体,它只规定子类必须具备这个能力。
3.2 抽象类不能直接实例化
抽象类不能直接创建对象。
abstract class Animal {
void eat();
}
void main() {
// Animal animal = Animal(); // 错误
}
原因很简单:抽象类可能有未实现的方法,直接创建对象没有意义。
3.3 子类实现抽象方法
子类继承抽象类后,需要实现抽象方法。
abstract class Animal {
void eat();
}
class Dog extends Animal {
@override
void eat() {
print('dog eat');
}
}
void main() {
Dog dog = Dog();
dog.eat();
}
这里 Dog 继承 Animal,并实现了 eat()。
3.4 抽象类可以有普通方法
抽象类不只能写抽象方法,也可以写已经实现好的普通方法。
abstract class Animal {
void eat();
void sleep() {
print('animal sleep');
}
}
class Cat extends Animal {
@override
void eat() {
print('cat eat');
}
}
void main() {
Cat cat = Cat();
cat.eat();
cat.sleep();
}
这意味着抽象类既可以规定规则,也可以提供公共实现。
3.5 抽象类适合什么场景
抽象类适合这些场景:
- 多个子类有共同能力。
- 需要强制子类实现某些方法。
- 父类本身不应该被直接实例化。
- 需要在父类中提供一部分公共实现。
例如支付系统:
abstract class Payment {
void pay(double amount);
void printReceipt() {
print('print receipt');
}
}
class WeChatPay extends Payment {
@override
void pay(double amount) {
print('wechat pay $amount');
}
}
class AliPay extends Payment {
@override
void pay(double amount) {
print('alipay pay $amount');
}
}
Payment 规定支付能力,具体支付方式由子类实现。
四、混入 mixin
4.1 什么是 mixin
mixin 用于定义一组可以复用的能力。
mixin Logger {
void log(String message) {
print('log: $message');
}
}
它不像普通父类那样强调“是什么”,更强调“能做什么”。
4.2 使用 with 混入能力
类可以通过 with 使用 mixin。
mixin Logger {
void log(String message) {
print('log: $message');
}
}
class UserService with Logger {
void getUser() {
log('get user');
}
}
void main() {
UserService service = UserService();
service.getUser();
}
UserService 不是 Logger 的子类,但它获得了 Logger 里的 log() 能力。
4.3 一个类可以混入多个 mixin
Dart 是单继承语言,一个类只能继承一个父类。
但一个类可以混入多个 mixin。
mixin Logger {
void log(String message) {
print('log: $message');
}
}
mixin Cache {
void saveCache(String key) {
print('save cache: $key');
}
}
class DataService with Logger, Cache {
void loadData() {
log('load data');
saveCache('user_list');
}
}
这就是 mixin 的灵活之处:它能横向复用能力。
4.4 mixin 的 on 限制
有些 mixin 只能用于特定父类或特定类型。
可以使用 on 限制。
class Animal {
String name;
Animal(this.name);
}
mixin Run on Animal {
void run() {
print('$name is running');
}
}
class Dog extends Animal with Run {
Dog(super.name);
}
Run on Animal 表示这个 mixin 只能混入到 Animal 或其子类中。
这样 mixin 内部就可以安全使用 Animal 的成员,比如 name。
4.5 mixin 中的方法冲突
如果多个 mixin 中有同名方法,后面的 mixin 会覆盖前面的同名方法。
mixin A {
void hello() {
print('A');
}
}
mixin B {
void hello() {
print('B');
}
}
class Demo with A, B {}
void main() {
Demo demo = Demo();
demo.hello(); // B
}
所以使用多个 mixin 时,要注意方法命名和混入顺序。
五、抽象类和 mixin 的区别
抽象类和 mixin 都能复用代码,但它们表达的关系不一样。
| 对比 | 抽象类 | mixin |
|---|---|---|
| 关注点 | 规定一类对象的共同规则 | 复用一组能力 |
| 使用方式 | extends |
with |
| 关系含义 | 是什么 | 能做什么 |
| 数量限制 | 单继承 | 可多个混入 |
| 是否可有实现 | 可以 | 可以 |
| 常见用途 | 定义基类、模板、规范 | 横向复用能力 |
举个例子:
Dog extends Animal:狗是动物。Dog with Run:狗具备跑的能力。
如果表达“某类对象的本质分类”,优先考虑抽象类。
如果表达“多个不同类都能复用的能力”,优先考虑 mixin。
六、常见误区
6.1 把抽象类当普通类用
抽象类不能直接实例化。
它更像规范或模板,而不是具体对象。
6.2 所有复用都用继承
继承是强关系,表示 “is-a”。
如果只是多个类都需要同一组能力,mixin 往往更灵活。
6.3 mixin 里随便依赖外部字段
如果 mixin 中要使用某个父类成员,应该考虑用 on 限制类型。
否则混入到不合适的类中,代码会变得不安全、不清楚。
6.4 忽略多个 mixin 的顺序
多个 mixin 有同名方法时,后面的会覆盖前面的。
class Demo with A, B {}
这里 B 的同名方法优先级更高。
七、总结
这一篇我们系统讲了 Dart 中的抽象类和混入。
抽象类用于定义共同规则。它可以包含抽象方法,也可以包含普通方法,但不能直接实例化。子类继承抽象类后,需要实现抽象方法。
mixin 用于复用一组能力。类可以通过 with 混入一个或多个 mixin,也可以使用 on 限制 mixin 只能用于特定类型。
最后记住这组区别:
- 抽象类强调“是什么”。
- mixin 强调“能做什么”。
- 继承适合纵向分类。
- mixin 适合横向能力复用。
浙公网安备 33010602011771号