Dart 3.0在語法層面共發佈了3個高級特性,第一個特性Record記錄我們在前面已經學習和探究。今天我們來學習第二個高級類型Pattern模式,由於內容較多,共分2篇文章進行介紹,本文首先介紹模式的概覽和用法,包括匹配、解構、在變數申明、賦值、迴圈、表達式等應用場景…… ...
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
本文作者:奔跑的蝸牛,轉載請註明原文鏈接:https://ntopic.cn