【Dart语言】七、类修饰符

发布于:2025-04-12 ⋅ 阅读:(40) ⋅ 点赞:(0)

概况与用法

版本提示

除 abstract 修饰符外,所有其他类修饰符均要求 Dart 语言版本不低于 3.0。

类修饰符用于控制类或混入的使用方式,无论是在其自身库内部,还是在定义该库的外部。

修饰符关键字必须位于类或混入声明之前。例如,使用 abstract class 可以定义一个抽象类。类声明前可用的完整修饰符集合包括:

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

只有 base 修饰符可以出现在混入 (mixin) 声明之前。这些修饰符不适用于其他声明,如 enum 、typedef 、extension 或 extension type。

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

提示

若您维护代码库,请查阅《API维护者类修饰符指南》,了解如何为您的库适配此变更。

无修饰符

若需允许从任意代码库无限制地实例化或继承,请使用无修饰符的 class 或 mixin 声明。默认情况下,您可以:

  • 实例化类
  • 通过继承创建子类
  • 实现类或混入的接口
  • 混入mixin或mixin类

abstract

要定义一个无需完整实现全部接口的类,请使用 abstract 修饰符。

抽象类不可被任何库实例化(无论其定义库还是外部库),且通常包含抽象方法。

a.dart:

/// Vehicle 是一个抽象类
abstract class Vehicle {
  void moveForward(int meters); // 抽象方法
}

 b.dart:

import 'a.dart';

// 错误:不可实例化
Vehicle myVehicle = Vehicle();

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

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

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

若需使抽象类具备可实例化的特性,请定义工厂构造函数。

abstract class Vehicle {
  void moveForward(int meters); // 抽象方法
  
  factory Vehicle() => return Car(); // 工厂函数(实例化实现类)
}

// 可被实现(Vehicle抽象类里有工厂构造函数时不能被继承)
class Car implements Vehicle {
  int passengers = 4;

  @override
  void moveForward(int meters) => print("这是一辆$meters米长的小车");
}

void main() {
  Vehicle myVehicle = Vehicle(); // myVehicle 实际上是一个 Car 类型
  myVehicle.moveForward(4);
}

base

为强制要求类或混入 (mixin) 必须通过继承实现,请使用 base 修饰符。基类禁止在定义库之外被实现,这保证了:

  • 创建子类型实例时必定调用基类构造函数。
  • 所有已实现的私有成员都在子类型中存在。
  • base 类新增成员不会破坏子类型(子类型自动继承新成员)。
  • 除非子类型已存在同名且签名不兼容的成员。

任何实现或继承基类的类,都必须被标记为 base 、final 或 sealed。这一机制可防止外部库破坏基类所确保的各项特性。

a.dart:

base class Vehicle {
  void moveForward(int meters) {
    // ...
  }
}

 b.dart:

import 'a.dart';

// 可被实例化
Vehicle myVehicle = Vehicle();

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

// 错误:不可被实现(会报:Vehicle类作为基类,不能在其定义库之外被实现)
base class MockVehicle implements Vehicle {
  @override
  void moveForward() {
    // ...
  }
}

interface

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

  • 当类的实例方法调用 this 上的另一个实例方法时,它始终会执行来自同一库的已知方法实现。
  • 其他库无法以意外的方式重写接口类自身方法后续可能调用的方法,从而减少脆弱的基类问题。

 a.dart:

interface class Vehicle {
  void moveForward(int meters) {
    // ...
  }
}

 b.dart:

import 'a.dart';

// 可被实例化
Vehicle myVehicle = Vehicle();

// 错误:不可继承
class Car extends Vehicle {
  int passengers = 4;
  // ...
}

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

注意

前面章节 “类与对象” 中的隐式接口小节介绍过,每个类都隐式定义了一个接口。接口所在库之外的库可以实现和继承该隐式接口。但使用 interface 修饰符的显式接口只能实现而不能继承该显式接口(如果同在一个库内则可以继承)。

abstract interface

使用 interface 修饰符最常见的场景是定义纯接口。将 interface 和 abstract 修饰符结合使用可创建 abstract interface class。

与 interface 类类似,其他库可以实现但不能继承纯接口。与 abstract 类类似,纯接口可以包含抽象成员。

final

要封闭类型层次结构,请使用 final 修饰符。这可以防止来自当前库外部的子类型化。通过同时禁止继承和实现,能够完全阻止子类型化。这保证了:

  • 您可以安全地向 API 添加增量更改。
  • 在调用实例方法时,您可以确信这些方法没有在第三方子类中被覆盖。

使用 final 修饰的类允许在同一库内被继承或实现(库外则不能)。final 修饰符同时具备 base 的约束效果,因此其所有子类也必须使用 base 、final 或 sealed 进行修饰。

a.dart:

final class Vehicle {
  void moveForward(int meters) {
    // ...
  }
}

b.dart:

import 'a.dart';

// 可被实例化
Vehicle myVehicle = Vehicle();

// 错误:不可继承
class Car extends Vehicle {
  int passengers = 4;
  // ...
}

// 错误:无法实现
class MockVehicle implements Vehicle {

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

sealed

要创建一个已知的、可枚举的子类型集合,请使用 sealed 修饰符。这允许您创建一个针对这些子类型的 switch 语句,且能静态确保覆盖所有可能情况。

sealed 修饰符会阻止类在自身所属库之外被继承或实现。sealed (密封)类默认是抽象的。

  • 它们自身不能被实例化。
  • 可以包含工厂构造函数。
  • 可以为它们的子类定义构造函数。

不过,密封类的子类默认不是抽象的。

编译器能够识别所有可能的直接子类型,因为这些子类型只能存在于同一库中。这一机制使得编译器可以在switch语句未能穷尽处理所有可能的子类型时发出警告:

sealed class Vehicle {}

class Car extends Vehicle {}

class Truck implements Vehicle {}

class Bicycle extends Vehicle {}

// 错误:无法实例化(密封类默认是抽象的,抽象类不能实例化)
Vehicle myVehicle = Vehicle();

// 子类可以被实例化
Vehicle myCar = Car();

String getVehicleSound(Vehicle vehicle) {
  // 错误:switch语句缺少对Bicycle子类型的处理或default case
  return switch (vehicle) {
    Car() => 'vroom',
    Truck() => 'VROOOOMM',
  };
}

若不需要穷尽性匹配,或希望后续能添加子类型而不破坏API兼容性,请使用 final 修饰符。如需更深入的比较,请阅读 sealed 与 final 的对比。

修饰符组合

您可以通过组合修饰符来实现多层次的限制。类声明按顺序可以包含以下元素:

  1、(可选)abstract :决定类是否可以包含抽象成员并阻止实例化。
  2、(可选)base 、interface 、final 或 sealed 之一:限制其他库对该类的子类型化。
  3、(可选)mixin :决定该声明是否可以被混入。
  4、 class 关键字本身。

某些修饰符不能组合使用,因为它们存在矛盾、冗余或互斥关系:

  • abstract 与 sealed:密封类默认就是抽象的。
  • interface 、final 或 sealed 与 mixin:这些访问修饰符会阻止混入。

API维护者类修饰符指南

详情 >

类修饰符参考指南

详情 >


网站公告

今日签到

点亮在社区的每一天
去签到