dart-use-pattern-matching

dart-use-pattern-matching

熱門

在適當的情境下使用 switch 運算式與模式比對

2792星標
164分支
更新於 2026/8/5
SKILL.md
唯讀
名稱
dart-use-pattern-matching
描述

在適當的情境下使用 switch 運算式與模式比對

實作 Dart 模式比對 (Dart Patterns)

目錄

模式選擇策略

根據資料結構與預期結果選擇特定的模式型別。請遵循以下條件指南:

  • 若要驗證並從反序列化資料(例如 JSON)中擷取資料: 使用 Map 與 List 模式,同時檢查結構並解構鍵值對 (key-value pairs)。
  • 若要處理多個回傳值: 使用 Record 模式將欄位直接解構至區域變數中。
  • 若要執行特定型別的行為(代數資料型別 Algebraic Data Types): 將 Object 模式結合 sealed 類別使用,以確保涵蓋所有可能情況(窮舉性 exhaustiveness)。
  • 若要比對數值範圍或條件: 使用關係 (Relational, >=<=) 與邏輯與 (Logical-and, &&) 模式。
  • 若多個 case 共享相同邏輯: 使用邏輯或 (Logical-or, ||) 模式來共享單一 case 主體或 Guard 子句。
  • 若要忽略特定值: 在集合中使用萬用字元模式 (Wildcard pattern, _) 或不進行比對的 Rest 元素 (...)。

Switch 敘述 vs. 運算式

根據執行的上下文情境選擇合適的 switch 結構:

  • 若要產生數值: 使用 switch 運算式 (switch expression)
    • 語法:switch (value) { pattern => expression, }
    • 規則:每個 case 必須是單一運算式。不會隱式貫穿 (no implicit fallthrough)。必須具備窮舉性(涵蓋所有情況)。
  • 若要執行敘述或產生副作用: 使用 switch 敘述 (switch statement)
    • 語法:switch (value) { case pattern: statements; }
    • 規則:空白的 case 會貫穿 (fall through) 至下一個 case。非空白的 case 則會隱式中斷(不需要使用 break 關鍵字)。

核心模式實作

使用以下語法與規則實作模式:

  • 邏輯或 (||): pattern1 || pattern2。兩個分支必須定義完全相同的一組變數。
  • 邏輯與 (&&): pattern1 && pattern2。分支之間不可定義重疊的變數。
  • 關係 (Relational): ==!=<><=>= 後接常數運算式。
  • 型別轉置/轉型 (as): pattern as Type。若數值不符合該型別則拋出例外。用於在解構過程中強制斷言 (assert) 型別。
  • Null 檢查 (?): pattern?。若數值為 null 則比對失敗。將變數綁定至不可為 null (non-nullable) 的基礎型別。
  • Null 斷言 (!): pattern!。若數值為 null 則拋出例外。
  • 變數 (Variable): var nameType name。將比對成功的數值綁定至新的區域變數。
  • 萬用字元 (_): 比對任意數值並予以丟棄。
  • List: [pattern1, pattern2]。比對長度完全符合的列表,除非使用了 Rest 元素(......var rest)。
  • Map: {"key": pattern}。比對包含指定鍵 (key) 的 Map。忽略未比對的鍵。
  • Record: (pattern1, named: pattern2)。比對結構完全相同的 Record。使用 :var name 可自動推導 getter 的名稱。
  • Object: ClassName(field: pattern)。比對 ClassName 的實例。使用 :var field 可自動推導 getter 的名稱。

工作流程

任務進度:實作模式比對

在實作複雜的模式比對邏輯時,可複製此檢查清單來追蹤進度:

  • [ ] 確定正在評估的資料結構(JSON、Record、Class、Enum)。
  • [ ] 選擇合適的 switch 結構(計算數值用運算式 Expression,副作用用敘述 Statement)。
  • [ ] 定義所需的模式(Object、Map、List、Record)。
  • [ ] 使用變數模式 (var x:var y) 擷取所需的資料。
  • [ ] 針對無法透過模式表達的邏輯,套用 Guard 子句 (when condition)。
  • [ ] 使用萬用字元 (_) 或 default 子句處理未比對的情況(若未使用 sealed 類別)。
  • [ ] 執行窮舉性 (exhaustiveness) 驗證器。

回饋循環:窮舉性檢查

當對 sealed 類別或 enum 進行 switch 比對時,必須確保所有子型別都已處理。

  1. 執行驗證器: 執行 dart analyze
  2. 檢視錯誤: 尋找「The type 'X' is not exhaustively matched by the switch cases」相關錯誤訊息。
  3. 修正問題: 為未處理的子型別補上缺失的 Object 模式,或者若允許預設退回機制,可新增萬用字元 (_) case。

範例

JSON 驗證與解構

使用 Map 與 List 模式在單一步驟中完成結構驗證與資料擷取。

輸入:

var data = {
  'user': ['Lily', 13],
};

實作:

if (data case {'user': [String name, int age]}) {
  print('User $name is $age years old.');
} else {
  print('Invalid JSON structure.');
}

代數資料型別(Sealed 類別)

將 Object 模式搭配 switch 運算式使用,以窮舉方式處理型別家族。

實作:

sealed class Shape {}

class Square implements Shape {
  final double length;
  Square(this.length);
}

class Circle implements Shape {
  final double radius;
  Circle(this.radius);
}

// 由於 `sealed` 修飾詞,switch 運算式能保證涵蓋所有情況(窮舉性)。
double calculateArea(Shape shape) => switch (shape) {
  Square(length: var l) => l * l,
  Circle(:var radius)   => math.pi * radius * radius,
};

變數對調與解構

使用變數賦值模式來對調數值,或在不使用暫存變數的情況下擷取 Record 欄位。

實作:

var (a, b) = ('left', 'right');
(b, a) = (a, b); // 對調數值

// 解構函式回傳值
var (name, age) = getUserInfo();

Guard 子句與邏輯或

在模式比對成功後,使用 when 來評估自訂條件。

實作:

switch (shape) {
  case Square(size: var s) || Circle(size: var s) when s > 0:
    print('Valid symmetric shape with size $s');
  case Square() || Circle():
    print('Invalid or empty shape');
  default:
    print('Unknown shape');
}