Class modifiers

Class modifiers

版本说明
除了 abstract 之外,类修饰符需要 Dart 语言版本至少为 3.0。

类修饰符控制了类或 mixin 的使用方式,无论是在其自己的库内部,还是在其定义所在的库外部。

修饰符关键字位于类或 mixin 声明之前。例如,abstract class 定义了一个抽象类。可以出现在类声明之前的完整修饰符集合包括:

  • abstract
  • base
  • final
  • interface
  • sealed
  • mixin

只有 base 修饰符可以出现在 mixin 声明之前。这些修饰符不适用于其他声明,如 enumtypedefextensionextension type

在决定是否使用类修饰符时,请考虑类的预期用途以及该类需要依赖哪些行为。

提示
如果你已经熟悉 Dart 的类修饰符,只想查看它们的组合行为大纲或复习一下,请查阅类修饰符参考

如果你维护一个库,请阅读面向 API 维护者的类修饰符页面,以获取有关如何为你的库应对这些更改的指导。

无修饰符

要允许在任何库中无限制地进行构造或子类型化,请使用不带修饰符的类或 mixin 声明。默认情况下,你可以:

  • 构造类的新实例。
  • 继承一个类以创建新的子类型。
  • 实现一个类或 mixin 的接口。
  • 混入一个 mixin 或 mixin class。

abstract

要定义一个不需要为其整个接口提供完整、具体实现的类,请使用 abstract 修饰符。

抽象类不能在任何库中被构造,无论是其自己的库还是外部库。抽象类通常有抽象方法。

a.dart

1
2
3
abstract class Vehicle {
void moveForward(int meters);
}

b.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import 'a.dart';

// 错误:`Vehicle` 不能被实例化,因为它被标记为 `abstract`。
Vehicle myVehicle = Vehicle();

// 可以被继承。
class Car extends Vehicle {
int passengers = 4;

@override
void moveForward(int meters) {
// ...
}
}

// 可以被实现。
class MockVehicle implements Vehicle {
@override
void moveForward(int meters) {
// ...
}
}

如果你希望你的抽象类看起来是可实例化的,请定义一个工厂构造函数。

base

要强制继承一个类或 mixin 的实现,请使用 base 修饰符。base 类禁止在其自己的库之外被实现。这保证了:

  • 每当创建该类的子类型的实例时,都会调用基类构造函数。
  • 所有已实现的私有成员都存在于子类型中。
  • 在基类中新增一个已实现的成员不会破坏子类型,因为所有子类型都会继承这个新成员。
    • 除非子类型已经声明了一个同名且签名不兼容的成员。

你必须将任何实现或继承 base 类的类标记为 basefinalsealed。这可以防止外部库破坏 base 类的保证。

a.dart

1
2
3
4
5
base class Vehicle {
void moveForward(int meters) {
// ...
}
}

b.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import 'a.dart';

// 可以被构造。
Vehicle myVehicle = Vehicle();

// 可以被继承。
base class Car extends Vehicle {
int passengers = 4;
// ...
}

// 错误:`Vehicle` 不能在不同的库中被实现,因为它被标记为 `base`。
base class MockVehicle implements Vehicle {
@override
void moveForward() {
// ...
}
}

interface

要定义一个接口,请使用 interface 修饰符。接口定义库之外的库可以实现该接口,但不能继承它。这保证了:

  • 当类的实例方法调用 this 上的另一个实例方法时,它将始终调用来自同一个库的已知方法实现。
  • 其他库不能覆写 interface 类自己的方法后续可能以意想不到的方式调用的方法。这减少了脆弱基类问题

a.dart

1
2
3
4
5
interface class Vehicle {
void moveForward(int meters) {
// ...
}
}

b.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import 'a.dart';

// 可以被构造。
Vehicle myVehicle = Vehicle();

// 错误:`Vehicle` 不能在不同的库中被继承,因为它被标记为 `interface`。
class Car extends Vehicle {
int passengers = 4;
// ...
}

// 可以被实现。
class MockVehicle implements Vehicle {
@override
void moveForward(int meters) {
// ...
}
}

abstract interface

interface 修饰符最常见的用途是定义一个纯接口。将 interfaceabstract 修饰符组合起来,可以得到一个**抽象接口类 (abstract interface class)**。

interface 类一样,其他库可以实现但不能继承一个纯接口。像 abstract 类一样,一个纯接口可以有抽象成员。

final

要关闭类型层次结构,请使用 final 修饰符。这会阻止在当前库之外对类进行子类型化。同时禁止继承和实现,从而完全阻止了子类型化。这保证了:

  • 你可以安全地对 API 进行增量更改。
  • 你可以调用实例方法,并确信它们没有在第三方子类中被覆写。

final 类可以在同一个库中被继承或实现。final 修饰符包含了 base 的效果,因此任何子类也必须被标记为 basefinalsealed

a.dart

1
2
3
4
5
final class Vehicle {
void moveForward(int meters) {
// ...
}
}

b.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import 'a.dart';

// 可以被构造。
Vehicle myVehicle = Vehicle();

// 错误:`Vehicle` 不能在不同的库中被继承,因为它被标记为 `final`。
class Car extends Vehicle {
int passengers = 4;
// ...
}

// 错误:`Vehicle` 不能在不同的库中被实现,因为它被标记为 `final`。
class MockVehicle implements Vehicle {
@override
void moveForward(int meters) {
// ...
}
}

sealed

要创建一个已知的、可枚举的子类型集合,请使用 sealed 修饰符。这允许你创建一个针对这些子类型的 switch 语句,并静态地确保其是**穷尽的 (exhaustive)**。

sealed 修饰符阻止一个类在其自己的库之外被继承或实现。密封类是隐式抽象的。

  • 它们本身不能被构造。
  • 它们可以有工厂构造函数。
  • 它们可以为它们的子类定义构造函数。

然而,密封类的子类并不是隐式抽象的。

编译器知道所有可能的直接子类型,因为它们只能存在于同一个库中。这使得编译器能够在 switch 语句的 case 没有穷尽地处理所有可能的子类型时提醒你:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
sealed class Vehicle {}

class Car extends Vehicle {}

class Truck implements Vehicle {}

class Bicycle extends Vehicle {}

// 错误:`Vehicle` 不能被实例化,因为它被标记为 `sealed`,
// 因此是隐式抽象的。
Vehicle myVehicle = Vehicle();

// sealed 类的子类可以被实例化,除非也受到限制。
Vehicle myCar = Car();

extension VehicleSounds on Vehicle {
String get sound {
// 错误:该 switch 语句没有穷尽地处理
// 所有可能的 `Vehicle` 类型对象。
// 在这个例子中,运行时类型为 `Bicycle` 的 `Vehicle` 对象
// 不会匹配任何一个 case。
return switch (this) {
Car() => 'vroom',
Truck() => 'VROOOOMM',
};
}
}

如果你不希望进行穷尽的 switch 检查,或者希望以后能够在不破坏 API 的情况下添加子类型,请使用 final 修饰符。更深入的比较,请阅读 sealedfinal 的对比

组合修饰符

你可以组合一些修饰符以实现分层的限制。一个类声明可以按顺序包含:

  1. (可选)abstract,描述该类是否可以包含抽象成员并阻止实例化。
  2. (可选)baseinterfacefinalsealed 之一,描述对其他库子类型化该类的限制。
  3. (可选)mixin,描述该声明是否可以被混入。
  4. class 关键字本身。

你不能组合某些修饰符,因为它们是矛盾的、冗余的或互斥的:

  • abstractsealed。一个 sealed 类是隐式抽象的。
  • interfacefinalsealedmixin。这些访问修饰符会阻止混入。

有关类修饰符如何组合的进一步指导,请查阅类修饰符参考

作者

wuhunyu

发布于

2025-09-11

更新于

2025-09-11

许可协议