跳到主内容

类修饰符

类修饰符控制类或混入(mixin)如何使用,包括 在其自身库中 和在其定义库之外。

修饰符关键字位于类或混入声明之前。例如,编写 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 类可以在同一库中进行扩展或实现。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 修饰符。这允许您对这些子类型进行切换,并静态确保其 穷尽性

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

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

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

编译器知道所有可能的直接子类型,因为它们只能存在于同一个库中。这使得编译器可以在 switch 语句未能穷尽处理其所有可能子类型的情况下提醒您

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

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