Pattern 模式比對

Dart 語言版本 ≥ 3.0,才可以使用Patterns

一、Pattern 核心作用

Pattern 是 Dart 獨立語法類別(和陳述式、運算式同層級),用來描述一組數值的結構樣貌,具備兩大核心能力,可同時混用:比對(Match)解構(Destructure)

  1. 比對 Match:檢查數值是否符合預期的結構樣式
    • 是否等於指定常數
    • 是否符合特定型別
    • 是否具備指定結構(List/Map/物件/記錄)
    • 是否滿足額外條件
  2. 解構 Destructure:比對成功後,快速取出數值內部欄位、元素,綁定至變數

1. 常數比對範例

void main() {
	int number = 1;
	switch (number) {
	  case 1:
		print('one'); // 常數模式比對 number == 1
	  default:
		print('其他數字');
	}
}Code language: Dart (dart)

執行結果

one

2. 巢狀子模式比對

模式支援巢狀子模式,逐層驗證內部結構

void main() {
  const a = 'a';
  const b = 'b';
  var obj = ['a', 'b'];
  switch (obj) {
    case [a, b]:
      print('$a, $b');
    default:
      print('不吻合');
  }
}Code language: Dart (dart)

執行結果

a, b

3. 基礎解構範例

var numList = [1, 2, 3];
// List模式解構清單元素,綁定新變數
var [x, y, z] = numList; 
print(x + y + z); // 6 x :1 y:2 z:3Code language: PHP (php)

4. 巢狀模式比對 + 解構

void main() {
  var list = ['b', 99];
  switch (list) {
    // 邏輯或模式:第一個元素為a或b,第二個值綁定變數c
    case ['a' || 'b', var c]:
      print(c); // 99
  }
}Code language: Dart (dart)

二、Pattern 常用範例

1. 區域變數宣告

用於解構指派,一次建立多個變數

// 記錄+清單巢狀模式解構
var (str, [n1, n2]) = ('text', [10, 20]);
print(str); //text
print(n1 + n2); //30Code language: Dart (dart)

2. 變數重新指派(不需暫存變數即可交換數值)

var (a, b) = ("左", "右");
(b, a) = (a, b); // 交換兩個變數的數值
print('$a $b'); // 右 左Code language: Dart (dart)

3. switch /if-case 分支(最常使用,搭配 when 守衛條件)

case 內全部是可驗證模式:比對成功進入該分支,失敗則往下一組case檢查

基礎多型別 case

void testSwitch(dynamic obj) {
  switch (obj) {
    case 1:
      print('數字1');
    case >= 10 && <= 20:
      print('10~20區間的數字');
    case (var a, var b):
      print('記錄:$a,$b');
    default:
      print('無吻合條件');
  }
}Code language: Dart (dart)

邏輯或模式:多組分支共用同一段邏輯

enum Color { red, yellow, blue, green }
void main() {
  Color color = Color.red;
  bool isPrimary = switch (color) {
    Color.red || Color.yellow || Color.blue => true,
    _ => false,
  };
  print(isPrimary); // true
}
Code language: PHP (php)

when 守衛子句(比對通過後再加額外過濾條件)

sealed class Shape {}
class Square { final double size; Square(this.size); }
class Circle { final double size; Circle(this.size); }

void checkShape(Shape shape) {
  switch (shape) {
    // Square、Circle共用比對邏輯,新增size>0條件
    case Square(size: var s) || Circle(size: var s) when s > 0:
      print('合法對稱圖形');
    default:
      print('不合法圖形');
  }
}Code language: Dart (dart)

when 與一般 if 的差異

void compareWhen((int, int) pair) {
  switch (pair) {
    case (int a, int b):
      if (a > b) print('前面數值較大');
      // if條件不成立:直接結束整個switch,後續case不會執行
    case (int a, int b) when a > b:
      print('【when守衛】前面數值較大');
      // when條件不成立:只跳過目前case,繼續比對下一組case
    case (int a, int b):
      print('前面數值較小或相等');
  }
}

void main() {
  print("===== 測試 (5, 2) a > b =====");
  compareWhen((5, 2));

  print("\n===== 測試 (2, 5) a < b =====");
  compareWhen((2, 5));
}Code language: Dart (dart)

4. for /for-in 迴圈(解構 MapEntry 實用度最高)


void main() {
	Map<String, int> hist = {'a': 23, 'b': 100};
	
	// 完整寫法
	for (var MapEntry(key: key, value: count) in hist.entries) {
	  print('$key$count');
	}
	
	// 簡寫語法:key:key 簡化為 :key
	for (var MapEntry(:key, value: count) in hist.entries) {
	  print('$key$count');
	}
}Code language: Dart (dart)

執行結果

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
a:23
b:100
a:23
b:100
Code language: CSS (css)

三、Pattern 實務常見使用場景

1:函數多個回傳值(記錄解構)

傳統寫法(繁瑣冗長)

(String, int) userInfo() => ("Lily", 13);
void main() {
  var info = userInfo();
  var name = info.$1;
  var age = info.$2;
}
Code language: Dart (dart)

模式解構簡寫

var (name, age) = userInfo();
// 具名欄位記錄解構
final (:name, :age) = getData(); // 回傳 (name: "Doug", age:25)Code language: Dart (dart)

完整範例


(String, int) userInfo() => ("Lily", 18);


({String name, int age}) getData() => (name: "Doug", age: 25);

void main() {
  {
    var (name, age) = userInfo();
    print("姓名:$name,年齡:$age");
  }

  // 區塊內部 name/age 不會和外部變數衝突
  {
    final (:name, :age) = getData();
    print("姓名:$name,年齡:$age");
  }
}Code language: Dart (dart)

2:類別實體解構(物件模式)

class Foo {
  final String one;
  final int two;
  Foo({required this.one, required this.two});
}

void main() {
  final Foo myFoo = Foo(one: "test", two: 66);
  var Foo(:one, :two) = myFoo;
  print('$one $two'); //test 66
}Code language: Dart (dart)

3:代數資料型別(密封類別 + switch 分支運算)

統一處理一群相關子類別,不需分散撰寫各個實體方法

import 'dart:math';

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

double calculateArea(Shape shape) => switch (shape) {
  Square(length: var l) => l * l,
  Circle(radius: var r) => pi * r * r,
};
Code language: Dart (dart)

完整實作範例

import 'dart:math';

// 密封父類別,子類別只能在同一檔案定義
sealed class Shape {}

// 正方形子類別
class Square implements Shape {
  final double length;
  Square(this.length);
}

// 圓形子類別
class Circle implements Shape {
  final double radius;
  Circle(this.radius);
}

// 透過 switch 運算式 + 物件模式比對,統一計算面積
double calculateArea(Shape shape) => switch (shape) {
  // 比對 Square,取出 length 綁定至變數 l
  Square(length: var l) => l * l,
  // 比對 Circle,取出 radius 綁定至變數 r
  Circle(radius: var r) => pi * r * r,
};

void main() {
  // 建立圖形實體
  Shape square = Square(5);
  Shape circle = Circle(3);

  // 計算面積
  double squareArea = calculateArea(square);
  double circleArea = calculateArea(circle);

  print("正方形面積:$squareArea");    // 25.0
  print("圓形面積:${circleArea.toStringAsFixed(2)}"); // 28.27
}Code language: Dart (dart)

執行結果

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
正方形面積:25.0
圓形面積:28.27Code language: CSS (css)

4:JSON 資料驗證與解構(大幅簡化多層判斷程式)

(未使用模式時)多層巢狀型別判斷,程式碼冗長難讀

void oldCheck(Map data) {
  if (data is Map<String, Object?> && data.containsKey('user')) {
    var user = data['user'];
    if (user is List && user.length == 2) {
      String name = user[0] as String;
      int age = user[1] as int;
      print('$name $age');
    }
  }
}Code language: Dart (dart)

if-case 模式:單行程式同時完成結構驗證、型別驗證、變數綁定

void newCheck(Map data) {
  if (data case {'user': [String name, int age]}) {
    print('使用者 $name,年齡 $age');
  }
}Code language: Dart (dart)

此模式會一次性驗證下列所有條件:

  1. data 是 Map 且不為空值
  2. 存在 user 鍵
  3. user 對應的值是長度為2的清單
  4. 清單第一個元素是 String、第二個是 int
  5. 自動綁定 name、age 區域變數

實際使用範例

void main() {
  // 1. 合法JSON結構:比對成功
  final validJson = {
    'user': ['Lily', 13]
  };
  newCheck(validJson); // 輸出:使用者 Lily,年齡 13

  // 2. 清單內型別不符:年齡為字串,比對失敗
  final wrongTypeJson = {
    'user': ['Tom', '16']
  };
  newCheck(wrongTypeJson); // 無輸出

  // 3. user清單元素不足:缺少年齡,比對失敗
  final shortJson = {
    'user': ['Jack']
  };
  newCheck(shortJson); // 無輸出

  // 4. 缺少user鍵:比對失敗
  final noUserJson = {
    'info': ['Bob', 20]
  };
  newCheck(noUserJson); // 無輸出
}

// 透過 if-case Map+List 模式一次完成結構、型別驗證與解構指派
void newCheck(Map data) {
  if (data case {'user': [String name, int age]}) {
    print('使用者 $name,年齡 $age');
  } else {
    print('資料格式不合法');
  }
}Code language: PHP (php)

Pattern 模式比對

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *