Dart官方文檔:https://dart.dev/language/patterns
重要説明:本博客基於Dart官網文檔,但並不是簡單的對官網進行翻譯,在覆蓋核心功能情況下,我會根據個人研發經驗,加入自己的一些擴展問題和場景驗證。
Pattern模式匹配的定義
官網定義:Patterns are a syntactic category in the Dart language, like statements and expressions. A pattern represents the shape of a set of values that it may match against actual values.
初看定義不太好理解,感覺有點繞,大概意思:模式是Dart語言的一種語法分類,就像聲明和表達式一樣。模式代表了一組實際值的形狀,這個形狀可以匹配到實際值。(特別注意:這裏的Pattern和正則表達式沒有任何關係!)
有幾個重要的概念:語法、形狀、匹配
- 語法:語法是一個編碼語言的基礎,可見模式在Dart中的重要程度。
- 形狀:或者説結構,就是一組實際值是如何組織在一起的一種抽象(結構定義)。
- 匹配:根據一組值的形狀,我們匹配到對應的值。
舉一個List列表的例子,可能不是完全恰當,但是可以幫忙我們理解模式的這段定義:
- 語法:
final aList = [1, 2, 3];這個是定義列表的語句,其中aList代表變量名,列表採用[]包裹,元素採用,分隔,最後;結束等等,這些都是Dart中的語法。 - 形狀:列表採用
[]包裹,元素採用,分隔,元素類型int由Dart自動推導出來,這些都是這一組值的形狀,就是長什麼樣。 - 匹配:
aList[0] == 1根據列表的語法和形狀,可以匹配到實際值。
Pattern模式的用途
Pattern模式主要作用:匹配值、解構值。匹配和解構可以同時作用,需要根據上下文和值的形狀或結構具體來看。
首先,模式可以讓我們確定某個值的一些信息,包括:
- 有一個明確的形狀(或者結構)。
- 是一個明確的常量。
- 它和某個值相等(即可用於比較)。
- 有一個明確的類型。
然後,模式解構可以用一種便利的語法,把這個值進行分解,還可以綁定到某個變量上面。
匹配
匹配就是校驗某個值是否符合我們預期,換句話説,我們是在檢測某個值是否符合某種結構且它的值與指定值相等。
我們在編碼過程中,很多邏輯其實都是在進行模式,舉例如下:
// 常數匹配:1 == number ?
switch (number) {
case 1:
print('one');
}
// 列表匹配:`obj`是一個2個元素列表
// 元素匹配:`obj`的2個元素值分別為`a`和`b`
const a = 'a';
const b = 'b';
switch (obj) {
case [a, b]:
print('$a, $b');
}
解構
當一個對象和一個模式相匹配,那麼這個模式可以訪問對象的數據,並可以把這個對象拆分成不同部分。換句話説,這個模式解構了這個對象。
代碼樣例:如下代碼,List列表解構,和解構模式中的嵌套匹配模式。
// 列表解構:`[a, b, c]`結構`numList`對象
// 1. 匹配:`[a, b, c]`代表了具有3個元素的列表
// 2. 拆分:列表的3個元素,分別賦值給了新的變量`a`、`b`和`cs`
var numList = [1, 2, 3];
var [a, b, c] = numList;
print(a + b + c);
// 列表模式:包含2個元素,且第1個元素是`a`或`b`,第2個元素賦值給變量`c`
switch (list) {
case ['a' || 'b', var c]:
print(c);
}
模式的應用場景
在Dart語言總,有幾個常見可以使用模式:
- 局部變量的申明和賦值。
for和for-in循環語句。if-case和switch-case語句。- 集合相關的控制流。
變量申明
我們可以在Dart允許本地變量聲明的任何地方使用模式變量聲明,模式變量申明必須由var或者final + 模式組成(這也是Dart的模式變量的語法)。
代碼樣例:如下代碼,使用模式,我們申明瞭a,b和c三個變量(並且完成賦值)。
var (a, [b, c]) = ('str', [1, 2]);
變量賦值
上小節變量申明的代碼樣例中,其實已經進行了模式變量賦值:首先進行模式匹配,然後解構對象,最終進行遍歷賦值。
代碼樣例:如下代碼,採用變量賦值模式,輕鬆進行了2個元素值交換,而無需使用第3個變量。
var (a, b) = ('left', 'right');
(b, a) = (a, b);
print('$a $b');
Switch和表達式模式
本文開頭的樣例其實已經提到,任何case的語句其實都包含了一個模式。在case中,可以應用任何的模式,變量賦值的作用域僅在Case語句內部。
Case模式可以匹配失敗,它允許控制流:
- 匹配並解構
switch對象。 - 匹配失敗,則繼續執行匹配。
switch (obj) {
// 匹配:1 == obj
case 1:
print('one');
// 匹配:[first, last]區間
case >= first && <= last:
print('in range');
// 匹配:Record記錄,包含2個字段
// 賦值:`a`和`b`局部變量(作用域:本Case內部)
case (var a, var b):
print('a = $a, b = $b');
default:
}
// 邏輯或模式:多個case共用
var isPrimary = switch (color) {
Color.red || Color.yellow || Color.blue => true,
_ => false
};
switch (shape) {
case Square(size: var s) || Circle(size: var s) when s > 0:
print('Non-empty symmetric shape');
}
for和for-in循環模式
主要作用:迭代和解構集合。
代碼樣例:如下代碼,for循環匹配模式,並解構和賦值給變量。
Map<String, int> hist = {
'a': 23,
'b': 100,
};
// 匹配:`MapEntry`類型,繼續匹配`key`和`value`命名字段子模式
// 賦值:調用`key`和`value`的`getter`並賦值給`key`和`value`變量
for (var MapEntry(key: key, value: count) in hist.entries) {
print('$key occurred $count times');
}
// 上訴代碼的簡寫
for (var MapEntry(:key, value: count) in hist.entries) {
print('$key occurred $count times');
}
其他場景模式
本文前面的章節,我們主要是展示Dart類型模式和解構,當然也包括(a, b)內容交換的例子。本章進一步學習其他的場景模式。
通過本章學習,主要解決我們幾個問題:
- 什麼時候我們需要用到模式,我們為什麼需要模式?
- 模式主要解決什麼類型的問題?
- 什麼樣的模式最適合?
解構多個返回值
在之前的學習中,Record記錄的用途之一就是聚合多個值,並讓函數返回多個值。模式能匹配並解構Record記錄,並賦值給局部變量。
代碼樣例:如下代碼,userInfo(json)返回一個位置字段的記錄,被解構並把位置值賦值給了name和age局部變量。
// Record記錄的使用
var info = userInfo(json);
var name = info.$1;
var age = info.$2;
// Record解構和賦值
var (name, age) = userInfo(json);
解構類實例
對象模式能匹配命名的對象類型,可以解構對象的數據,並調用對象屬性的getters方法進行賦值。
代碼樣例:如下代碼,命名類型Foo實例myFoo被解構並進行賦值給one和two變量。
final Foo myFoo = Foo(one: 'one', two: 2);
var Foo(:one, :two) = myFoo;
print('one $one, two $two');
代數數據類型
對象解構和Switch模式有助於編寫代數數據類型風格代碼,它比較適合以下幾種場景:
- 有一羣相關聯的類型。
- 每個類型都有一個相同的操作,但這個操作對每個類型而言又有差異。
- 我們希望把這個操作能一把實現,而不是把實現散落在每個類型中。
樣例代碼:如下代碼,Shape是一個父類,2個或更多的子類都有計算面積的方法,最終通過calculateArea()函數一把實現了。
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) => math.pi * r * r
};
校驗JSON格式
前面章節,我們學習了List和Map類型的匹配和解構,它們也適用於JSON的key-value鍵值對。
代碼樣例:如下代碼,在已知JSON格式的情況下,我們可以通過List和Map完成JSON的解構和賦值。
var json = {
'user': ['Lily', 13]
};
var {'user': [name, age]} = json;
但是,當JSON格式不明確的情況下,我們可以通過解構來校驗JSON的格式。
代碼樣例:如下代碼,我們通過case模式,完成了JSON數據的校驗和賦值。
if (json case {'user': [String name, int age]}) {
print('User $name is $age years old.');
}
如上代碼,Case模式的匹配和賦值操作如下:
json是一個非空的map,進一步匹配map模式。json包含一個名為user的屬性,且它是一個包含2個元素的list類型,list中2個元素類型分別為String和int。- 最終,list的2個元素分別賦值給了
name和age局部變量。
我的本博客原地址:https://ntopic.cn/p/2023100401