目录

类修饰符

类修饰符控制如何使用类或 mixin,无论是在自己的库中还是在定义它的库之外使用。

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

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

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

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

无修饰符

#

要允许从任何库无限制地构造或创建子类型,请使用没有修饰符的 classmixin 声明。默认情况下,您可以

  • 构造类的新实例。
  • 扩展类以创建新的子类型。
  • 实现类或 mixin 的接口。
  • 混入 mixin 或 mixin 类。

abstract

#

要定义不需要完整实现其整个接口的类,请使用 abstract 修饰符。

抽象类不能从任何库(无论是自己的库还是外部库)构造。抽象类通常具有抽象方法

a.dart
dart
abstract class Vehicle {
  void moveForward(int meters);
}
b.dart
dart
import 'a.dart';

// Error: Can't be constructed.
Vehicle myVehicle = Vehicle();

// Can be extended.
class Car extends Vehicle {
  int passengers = 4;

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

// Can be implemented.
class MockVehicle implements Vehicle {
  @override
  void moveForward(int meters) {
    // ...
  }
}

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

base

#

要强制执行类或 mixin 实现的继承,请使用 base 修饰符。基类不允许在其自身库之外实现。这保证了

  • 每当创建类的子类型的实例时,都会调用基类构造函数。
  • 所有已实现的私有成员都存在于子类型中。
  • base 类中新的已实现成员不会破坏子类型,因为所有子类型都继承新成员。
    • 除非子类型已声明具有相同名称和不兼容签名的成员,否则情况确实如此。

您必须将任何实现或扩展基类的类标记为 basefinalsealed。这可以防止外部库破坏基类保证。

a.dart
dart
base class Vehicle {
  void moveForward(int meters) {
    // ...
  }
}
b.dart
dart
import 'a.dart';

// Can be constructed.
Vehicle myVehicle = Vehicle();

// Can be extended.
base class Car extends Vehicle {
  int passengers = 4;
  // ...
}

// ERROR: Can't be implemented.
base class MockVehicle implements Vehicle {
  @override
  void moveForward() {
    // ...
  }
}

interface

#

要定义接口,请使用 interface 修饰符。接口自身定义库之外的库可以实现该接口,但不能扩展它。这保证了

  • 当类的实例方法在 this 上调用另一个实例方法时,它将始终调用来自同一库的该方法的已知实现。
  • 其他库不能以意想不到的方式覆盖接口类自身方法可能稍后调用的方法。这减少了脆弱的基类问题
a.dart
dart
interface class Vehicle {
  void moveForward(int meters) {
    // ...
  }
}
b.dart
dart
import 'a.dart';

// Can be constructed.
Vehicle myVehicle = Vehicle();

// ERROR: Can't be inherited.
class Car extends Vehicle {
  int passengers = 4;
  // ...
}

// Can be implemented.
class MockVehicle implements Vehicle {
  @override
  void moveForward(int meters) {
    // ...
  }
}

abstract interface

#

interface 修饰符最常见的用途是定义纯接口。组合 interfaceabstract 修饰符以获得 abstract interface class

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

final

#

要关闭类型层次结构,请使用 final 修饰符。这可以防止从当前库之外的类创建子类型。不允许继承和实现完全阻止子类型化。这保证了

  • 您可以安全地向 API 添加增量更改。
  • 您可以调用实例方法,并知道它们未在第三方子类中被覆盖。

最终类可以在同一库中扩展或实现。 final 修饰符包含 base 的效果,因此任何子类也必须标记为 basefinalsealed

a.dart
dart
final class Vehicle {
  void moveForward(int meters) {
    // ...
  }
}
b.dart
dart
import 'a.dart';

// Can be constructed.
Vehicle myVehicle = Vehicle();

// ERROR: Can't be inherited.
class Car extends Vehicle {
  int passengers = 4;
  // ...
}

class MockVehicle implements Vehicle {
  // ERROR: Can't be implemented.
  @override
  void moveForward(int meters) {
    // ...
  }
}

sealed

#

要创建已知的、可枚举的子类型集,请使用 sealed 修饰符。这允许您在这些子类型上创建一个静态确保穷举的 switch。

sealed 修饰符防止类在其自身库之外被扩展或实现。密封类是隐式的抽象

  • 它们本身不能被构造。
  • 它们可以具有工厂构造函数
  • 它们可以定义供其子类使用的构造函数。

但是,密封类的子类不是隐式抽象的。

编译器知道任何可能的直接子类型,因为它们只能存在于同一个库中。这使得编译器可以在 switch 没有在其 case 中穷尽处理所有可能的子类型时提醒您

dart
sealed class Vehicle {}

class Car extends Vehicle {}

class Truck implements Vehicle {}

class Bicycle extends Vehicle {}

// ERROR: Can't be instantiated.
Vehicle myVehicle = Vehicle();

// Subclasses can be instantiated.
Vehicle myCar = Car();

String getVehicleSound(Vehicle vehicle) {
  // ERROR: The switch is missing the Bicycle subtype or a default case.
  return switch (vehicle) {
    Car() => 'vroom',
    Truck() => 'VROOOOMM',
  };
}

如果您不想要穷举切换,或者希望能够在以后添加子类型而不破坏 API,请使用final 修饰符。有关更深入的比较,请阅读sealedfinal

组合修饰符

#

您可以组合一些修饰符以进行分层限制。类声明可以是按顺序

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

您不能组合某些修饰符,因为它们是矛盾的、多余的,或者在其他方面是互斥的

  • abstractsealed密封类是隐式的 抽象类。
  • interfacefinalsealedmixin。这些访问修饰符阻止混入

有关如何组合类修饰符的更多指导,请查看类修饰符参考