動態

詳情 返回 返回

Flutter/Dart第20天:Dart 3.0新特性之類型修飾符 - 動態 詳情

Dart官方文檔:https://dart.dev/language/class-modifiers

重要説明:本博客基於Dart官網文檔,但並不是簡單的對官網進行翻譯,在覆蓋核心功能情況下,我會根據個人研發經驗,加入自己的一些擴展問題和場景驗證。

類型修飾符主要是控制類或者Mixin如何被使用,包括在庫內部和外部使用。修飾符關鍵字出現在類型或Mixin申明的前面,如abstract class通過abstract修飾符定義了一個抽象類。

可用於聲明類的修飾符關鍵字列表如下:

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

約束:上面的修飾符列表,只有base能用於Mixin類型;同時,上訴修飾符不能用於包括enumtypedefextension等類型聲明。

實戰:當我們決定要使用修飾符時,可能需要考慮一下類的預期用途和類需要提供哪些行為。

無修飾符

當我們定義類或者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時(即:基類),那麼這個基類的實現只能基類所在庫內。這樣做的目的:

  • 每當創建子類實例時,基類的構造函數被調用
  • 所有已經實現的私有成員都在子類中
  • 在基類中新增加的成員會被所有子類繼承(除非:子類中申明瞭同名的成員但並不兼容的簽名。如:子類申明瞭同名方法,但是方法入參或者返回結果與基類不兼容)

實戰:為了保證基類不會被破壞,子類必須使用basefinal或者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修飾符特性,因此,子類必須使用basefinal或者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修飾符。關於finalsealed修飾符的深入比較,請稍等本博客的下一個博客介紹(請容許我賣個關子!)。

組合修飾符

通過組合修飾符,可以起到疊加限制效果。我們申明類時,按照順序,可以疊加的修飾符:

  • 可選的abstract修飾符:類包含抽象成員,且不能被實例化
  • 可選的baseinterfacefinalsealed修飾符:限制其他庫的子類型
  • 可選的mixin修飾符:類是否可被混入
  • 必選的class類關鍵字

部分修飾符是不能組合使用,因為他們可能多餘或者矛盾互斥:

  • abstract修飾符和sealed修飾符:原因是sealed隱含了abstract修飾符
  • interfacefinalsealed修飾符和mixin修飾符:原因是這些修飾符都禁止被混入

完整的有效的修飾符組合列表如下:

image


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


Add a new 評論

Some HTML is okay.