Stories

Detail Return Return

Flutter/Dart第09天:Dart高級特性Pattern模式的概覽和用法 - Stories Detail

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和正則表達式沒有任何關係!)

有幾個重要的概念:語法、形狀、匹配

  1. 語法:語法是一個編碼語言的基礎,可見模式在Dart中的重要程度。
  2. 形狀:或者説結構,就是一組實際值是如何組織在一起的一種抽象(結構定義)。
  3. 匹配:根據一組值的形狀,我們匹配到對應的值。

舉一個List列表的例子,可能不是完全恰當,但是可以幫忙我們理解模式的這段定義:

  1. 語法:final aList = [1, 2, 3];這個是定義列表的語句,其中aList代表變量名,列表採用[]包裹,元素採用,分隔,最後;結束等等,這些都是Dart中的語法。
  2. 形狀:列表採用[]包裹,元素採用,分隔,元素類型int由Dart自動推導出來,這些都是這一組值的形狀,就是長什麼樣。
  3. 匹配:aList[0] == 1根據列表的語法和形狀,可以匹配到實際值。

Pattern模式的用途

Pattern模式主要作用:匹配值、解構值。匹配和解構可以同時作用,需要根據上下文和值的形狀或結構具體來看。

首先,模式可以讓我們確定某個值的一些信息,包括:

  1. 有一個明確的形狀(或者結構)。
  2. 是一個明確的常量。
  3. 它和某個值相等(即可用於比較)。
  4. 有一個明確的類型。

然後,模式解構可以用一種便利的語法,把這個值進行分解,還可以綁定到某個變量上面。

匹配

匹配就是校驗某個值是否符合我們預期,換句話説,我們是在檢測某個值是否符合某種結構且它的值與指定值相等。

我們在編碼過程中,很多邏輯其實都是在進行模式,舉例如下:

// 常數匹配: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語言總,有幾個常見可以使用模式:

  1. 局部變量的申明賦值
  2. forfor-in循環語句。
  3. if-caseswitch-case語句。
  4. 集合相關的控制流

變量申明

我們可以在Dart允許本地變量聲明的任何地方使用模式變量聲明,模式變量申明必須由var或者final + 模式組成(這也是Dart的模式變量的語法)。

代碼樣例:如下代碼,使用模式,我們申明瞭abc三個變量(並且完成賦值)。

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模式可以匹配失敗,它允許控制流:

  1. 匹配並解構switch對象。
  2. 匹配失敗,則繼續執行匹配。
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)內容交換的例子。本章進一步學習其他的場景模式。

通過本章學習,主要解決我們幾個問題:

  1. 什麼時候我們需要用到模式,我們為什麼需要模式?
  2. 模式主要解決什麼類型的問題?
  3. 什麼樣的模式最適合?

解構多個返回值

在之前的學習中,Record記錄的用途之一就是聚合多個值,並讓函數返回多個值。模式能匹配並解構Record記錄,並賦值給局部變量。

代碼樣例:如下代碼,userInfo(json)返回一個位置字段的記錄,被解構並把位置值賦值給了nameage局部變量。

// Record記錄的使用
var info = userInfo(json);
var name = info.$1;
var age = info.$2;

// Record解構和賦值
var (name, age) = userInfo(json);

解構類實例

對象模式能匹配命名的對象類型,可以解構對象的數據,並調用對象屬性的getters方法進行賦值。

代碼樣例:如下代碼,命名類型Foo實例myFoo被解構並進行賦值給onetwo變量。

final Foo myFoo = Foo(one: 'one', two: 2);
var Foo(:one, :two) = myFoo;
print('one $one, two $two');

代數數據類型

對象解構和Switch模式有助於編寫代數數據類型風格代碼,它比較適合以下幾種場景:

  1. 有一羣相關聯的類型。
  2. 每個類型都有一個相同的操作,但這個操作對每個類型而言又有差異。
  3. 我們希望把這個操作能一把實現,而不是把實現散落在每個類型中。

樣例代碼:如下代碼,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格式

前面章節,我們學習了ListMap類型的匹配和解構,它們也適用於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模式的匹配和賦值操作如下:

  1. json是一個非空的map,進一步匹配map模式
  2. json包含一個名為user的屬性,且它是一個包含2個元素的list類型,list中2個元素類型分別為Stringint
  3. 最終,list的2個元素分別賦值給了nameage局部變量。

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


Add a new Comments

Some HTML is okay.