Patterns模式
模式是 Dart 语言中的一个语法类别,就像语句和表达式一样。模式表示一组可以与实际值匹配的值的形状。
Patterns可以做什么
一般来说,模式可以匹配一个值、解构一个值或者两者兼而有之,这取决于模式的上下文和形状。
首先,模式匹配允许您检查给定值是否:
- 具有一定的形状。
- 是一定的常数。
- 等于其他东西。
- 具有一定的类型
然后,模式解构为您提供了一种方便的声明性语法,可将该值分解为其组成部分。同一模式还可以让您在此过程中将变量绑定到部分或全部部分。
匹配
模式总是针对某个值进行测试,以确定该值是否具有您期望的形式。换句话说,您正在检查该值是否与模式匹配。
匹配的构成取决于您使用的模式类型。例如,如果值等于模式的常量,则常量模式匹配:
switch (number) {
// Constant pattern matches if 1 == number.
case 1:
print('one');
}
许多模式使用子模式,有时分别称为外部模式和内部 模式。模式在其子模式上递归匹配。例如,任何集合类型模式的各个字段可以是变量模式或常量模式:
const a = 'a';
const b = 'b';
switch (obj) {
// 如果 obj 是具有两个字段的列表,则列表模式 [a, b] 首先匹配 obj,
// then if its fields match the constant subpatterns 'a' and 'b'.
case [a, b]:
print('$a, $b');
}
要忽略匹配值的部分内容,可以使用通配符模式 作为占位符。对于列表模式,可以使用rest 元素。
解构
当对象和模式匹配时,模式就可以访问对象的数据并分部分提取。换句话说,模式解构了对象:
var numList = [1, 2, 3];
// List pattern [a, b, c] destructures the three elements from numList...
var [a, b, c] = numList;
// ...and assigns them to new variables.
print(a + b + c);
你可以在解构模式中嵌套任何类型的模式。例如,这个 case 模式匹配并解构一个双元素列表,其第一个元素是'a'或'b':
switch (list) {
case ['a' || 'b', var c]:
print(c);
}
您可以在 Dart 语言的多个地方使用模式:
- 局部变量声明和赋值
- for 和 for-in 循环
- if-case和switch-case
- 集合文字中的控制流
变量声明
您可以在 Dart 允许局部变量声明的任何地方使用模式变量声明。模式与声明右侧的值匹配。匹配后,它会解构该值并将其绑定到新的局部变量:
// Declares new variables a, b, and c.
var (a, [b, c]) = ('str', [1, 2]);
模式变量声明必须以 或 开头var,final后跟模式。
变量赋值
变量赋值模式位于赋值的左侧。首先,它会解构匹配的对象。然后,它会将值分配给现有变量,而不是绑定新变量。
使用变量赋值模式交换两个变量的值,而无需声明第三个临时变量:
var (a, b) = ('left', 'right');
(b, a) = (a, b); // Swap.
print('$a $b'); // Prints "right left".
Switch 语句和表达式
每个 case 子句都包含一个模式。这适用于switch语句和表达式,以及if-case语句。您可以在 case 中使用任何类型的模式。
案例模式是可反驳的。它们允许控制流到以下任一方向:
- 匹配并解构正在打开的对象。
- 如果对象不匹配则继续执行。
模式在案例中解构的值将成为局部变量。它们的作用域仅限于该案例的主体内。
switch (obj) {
// Matches if 1 == obj.
case 1:
print('one');
// Matches if the value of obj is between the
// constant values of 'first' and 'last'.
case >= first && <= last:
print('in range');
// Matches if obj is a record with two fields,
// then assigns the fields to 'a' and 'b'.
case (var a, var b):
print('a = $a, b = $b');
default:
}
逻辑或模式对于在 switch 表达式或语句中让多个案例共享一个主体很有用:
var isPrimary = switch (color) {
Color.red || Color.yellow || Color.blue => true,
_ => false
};
Switch 语句可以让多个案例共享一个主体而不使用逻辑或模式,但它们对于允许多个案例共享一个保护仍然非常有用
switch (shape) {
case Square(size: var s) || Circle(size: var s) when s > 0:
print('Non-empty symmetric shape');
}
保护子句将任意条件作为案例的一部分进行评估,如果条件为假,则不会退出切换(就像if在案例主体中使用语句会导致的那样)。
switch (pair) {
case (int a, int b):
if (a > b) print('First element greater');
// If false, prints nothing and exits the switch.
case (int a, int b) when a > b:
// If false, prints nothing but proceeds to next case.
print('First element greater');
case (int a, int b):
print('First element not greater');
}
For 和 for-in 循环
您可以在for 和 for-in 循环中使用模式来迭代和解构集合中的值。
此示例在 for-in 循环中使用对象解构MapEntry来解构调用返回的对象.entries:
Map<String, int> hist = {
'a': 23,
'b': 100,
};
for (var MapEntry(key: key, value: count) in hist.entries) {
print('$key occurred $count times');
}
对象模式检查hist.entries具有命名类型MapEntry,然后递归到命名字段子模式key和value。它在每次迭代中调用上的keygetter 和valuegetter MapEntry,并将结果分别绑定到局部变量key和count。
将 getter 调用的结果绑定到同名变量是一种常见用例,因此对象模式也可以从变量 子模式推断出 getter 名称。这允许您将变量模式从冗余简化key: key为:key:
for (var MapEntry(:key, value: count) in hist.entries) {
print('$key occurred $count times');
}
模式用例
上一节 介绍了模式如何融入其他 Dart 代码构造。您看到了一些有趣的用例作为示例,例如交换 两个变量的值,或 解构映射中的键值对 。本节将介绍更多用例,回答:
- 何时以及为何可能需要使用模式。
- 他们解决了哪些类型的问题。
- 它们最适合哪些成语。
解构多个返回值
记录允许从单个函数调用聚合并返回多个值。模式增加了将记录字段直接解构为局部变量的能力,与函数调用一致。
而不是为每个记录字段单独声明新的局部变量,如下所示:
var info = userInfo(json);
var name = info.$1;
var age = info.$2;
您可以使用变量声明或 分配模式将函数返回的记录的字段解构为局部变量,并使用记录模式 作为其子模式:
var (name, age) = userInfo(json);
要使用模式解构具有命名字段的记录:
final (:name, :age) =
getData(); // For example, return (name: 'doug', age: 25);
解构类实例
对象模式与命名对象类型匹配,允许您使用对象类已经公开的 getter 来解构其数据。
要解构某个类的实例,请使用命名类型,后跟括号中要解构的属性:
final Foo myFoo = Foo(one: 'one', two: 2);
var Foo(:one, :two) = myFoo;
print('one $one, two $two');
代数数据类型
对象解构和 switch 语句有利于以代数数据类型样式编写代码。在以下情况下使用此方法:
- 您有一个相关类型的家族。
- 您有一个需要针对每种类型采取特定行为的操作。
- 您希望将该行为分组到一个地方,而不是将其分散到所有不同的类型定义中。
不要将操作作为每个类型的实例方法实现,而是将操作的变化保存在切换子类型的单个函数中:
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
var json = {
'user': ['Lily', 13]
};
var {'user': [name, age]} = json;
如果您知道 JSON 数据具有您期望的结构,则前面的示例是现实的。但数据通常来自外部来源,例如通过网络。您需要先验证它以确认其结构。
如果没有模式,验证将会非常冗长:
镖
if (json is Map<String, Object?> &&
json.length == 1 &&
json.containsKey('user')) {
var user = json['user'];
if (user is List<Object> &&
user.length == 2 &&
user[0] is String &&
user[1] is int) {
var name = user[0] as String;
var age = user[1] as int;
print('User $name is $age years old.');
}
}
单一案例模式 可以实现相同的验证。单一案例最适合用作if-case语句。模式提供了一种更具声明性且更简洁的 JSON 验证方法:
if (json case {'user': [String name, int age]}) {
print('User $name is $age years old.');
}
此案例模式同时验证了:
-
json是一个地图,因为它必须首先匹配外部地图模式才能继续。
- 而且,因为它是一张地图,所以它也确认json不为空。
-
json包含一个密钥user。
-
该键user与两个值的列表配对。
-
列表值的类型为String和int。
-
用于保存值的新局部变量是name和age。