Dart官方文檔:https://dart.dev/language/class-modifiers
重要説明:本博客基於Dart官網文檔,但並不是簡單的對官網進行翻譯,在覆蓋核心功能情況下,我會根據個人研發經驗,加入自己的一些擴展問題和場景驗證。
類型修飾符主要是控制類或者Mixin如何被使用,包括在庫內部和外部使用。修飾符關鍵字出現在類型或Mixin申明的前面,如abstract class通過abstract修飾符定義了一個抽象類。
可用於聲明類的修飾符關鍵字列表如下:
- abstract
- base
- final
- interface
- sealed
- mixin
約束:上面的修飾符列表,只有base能用於Mixin類型;同時,上訴修飾符不能用於包括enum、typedef和extension等類型聲明。
實戰:當我們決定要使用修飾符時,可能需要考慮一下類的預期用途和類需要提供哪些行為。
無修飾符
當我們定義類或者Mixin時,不希望對構造函數或者子類進行限制時,我們可以不使用修飾符。
當類或者Mixin沒有修飾符時,默認情況下,可以對這些類或者Mixin進行以下操作:
- 通過構造函數創建類實例
- 通過繼承類來創建子類
- 實現類或者Mixin的接口
- 混入Mixin或者Mixin類
abstract修飾符(抽象類)
使用場景:當我們定義了一個類(即:抽象類),但又沒有完整地實現了它所有的接口時使用(和Java語言一樣),請使用abstract修飾符。
約束:抽象類不能被實例化;一般情況,抽象類都包含抽象方法。
// 抽象類
abstract class Vehicle {
void moveForward(int meters);
}
// 實現類
class MockVehicle implements Vehicle {
@override
void moveForward(int meters) {
// ...
}
}
base修飾符(基類)
使用場景:當我們用base修飾符定義了一個類或者Mixin時(即:基類),那麼這個基類的實現只能基類所在庫內。這樣做的目的:
- 每當創建子類實例時,基類的構造函數被調用
- 所有已經實現的私有成員都在子類中
- 在基類中新增加的成員會被所有子類繼承(除非:子類中申明瞭同名的成員但並不兼容的簽名。如:子類申明瞭同名方法,但是方法入參或者返回結果與基類不兼容)
實戰:為了保證基類不會被破壞,子類必須使用base,final或者sealed修飾符。
如下代碼樣例,基類可以實例化、被繼承,但是不能被實現:
// 基類
base class Vehicle {
void moveForward(int meters) {
// ...
}
}
// 1. 實例化
Vehicle myVehicle = Vehicle();
// 2. 被繼承
base class Car extends Vehicle {
int passengers = 4;
// ...
}
// 3. ERROR:不能被實現
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';
// 1. 實例化
Vehicle myVehicle = Vehicle();
// 2. 被實現
class MockVehicle implements Vehicle {
@override
void moveForward(int meters) {
// ...
}
}
// 3. ERROR: 不能被繼承
class Car extends Vehicle {
int passengers = 4;
// ...
}
abstrace interface(抽象接口類)
一般情況下,我們使用interface來定義純粹接口。
當我們使用abstract interface class組合修飾符時,可以定義一個抽象接口類:它即有接口類的功能(可被實現,但不能被繼承),也有抽象類的功能(有抽象成員)。
final修飾符(不可變類)
使用場景:當使用final修飾符時,表示該類不能被其他庫繼承和實現(和Java還有點不一樣)。這樣做的目的:
- 可以安全地進行API變更
- 該類不會被第三方子類覆蓋,因此可以放心調用實例方法
約束:final不可變類可以在本庫中被繼承和實現,final修飾符包含了base修飾符特性,因此,子類必須使用base,final或者sealed修飾符。
// a.dart 接口類
final class Vehicle {
void moveForward(int meters) {
// ...
}
}
//
// b.dart
//
import 'a.dart';
// 1. 實例化
Vehicle myVehicle = Vehicle();
// 2. ERROR: 不能被繼承
class Car extends Vehicle {
int passengers = 4;
// ...
}
class MockVehicle implements Vehicle {
// 3. ERROR: 不能被實現
@override
void moveForward(int meters) {
// ...
}
}
sealed修飾符(密封類)
使用場景:當我們定義了一個類(即:密封類),且明確該類的所有子類集合時,請使用sealed修飾符。這允許我們通過switch窮舉所有的子類型。
約束:sealed修飾的類,禁止被其他庫繼承或者實現,它隱含abstract修飾符:
- 不能被實例化
- 可以有工廠構造函數
- 可以定義構造函數,子類可直接使用
- 子類並不是
abstract抽象類
編譯器可以知道所有sealed修飾符類的子類(因為他們在同一個庫中),這樣在switch中,如未窮舉,編譯器能發出錯誤警告!
// 密封類
sealed class Vehicle {}
class Car extends Vehicle {}
class Truck implements Vehicle {}
class Bicycle extends Vehicle {}
// 1. ERROR: 不能被實例化
Vehicle myVehicle = Vehicle();
// 2. 子類可以被實例化
Vehicle myCar = Car();
String getVehicleSound(Vehicle vehicle) {
// 3. ERROR: switch中子類未窮舉(還有Bicycle子類)
return switch (vehicle) {
Car() => 'vroom',
Truck() => 'VROOOOMM',
};
}
在switch中,如果我們不想窮舉sealed類的子類;又或者以後還會增加子類,但又不想破壞API設計,我也可以使用final修飾符。關於final和sealed修飾符的深入比較,請稍等本博客的下一個博客介紹(請容許我賣個關子!)。
組合修飾符
通過組合修飾符,可以起到疊加限制效果。我們申明類時,按照順序,可以疊加的修飾符:
- 可選的
abstract修飾符:類包含抽象成員,且不能被實例化 - 可選的
base、interface、final和sealed修飾符:限制其他庫的子類型 - 可選的
mixin修飾符:類是否可被混入 - 必選的
class類關鍵字
部分修飾符是不能組合使用,因為他們可能多餘或者矛盾互斥:
abstract修飾符和sealed修飾符:原因是sealed隱含了abstract修飾符interface、final或sealed修飾符和mixin修飾符:原因是這些修飾符都禁止被混入
完整的有效的修飾符組合列表如下:

我的本博客原地址:https://ntopic.cn/p/2023110501