跳到主要内容

类修饰符

类修饰符控制如何使用类或混入,包括在其自己的库中和从定义它的库外部使用。

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

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

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

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

无修饰符

#

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

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

#

要强制继承类或混入的实现,请使用 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,该 switch 在静态上被确保是穷尽的

sealed 修饰符阻止类在其自身库外部被扩展或实现。密封类隐式为抽象类

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

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

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

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。这些访问修饰符阻止混入

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