Flutter/Dart第09天:Dart高级特殊Pattern模式的概览和用法

金歌  金牌会员 | 2023-10-13 12:09:46 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 577|帖子 577|积分 1731

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. // 常数匹配:1 == number ?
  2. switch (number) {
  3.   case 1:
  4.     print('one');
  5. }
  6. // 列表匹配:`obj`是一个2个元素列表
  7. // 元素匹配:`obj`的2个元素值分别为`a`和`b`
  8. const a = 'a';
  9. const b = 'b';
  10. switch (obj) {
  11.   case [a, b]:
  12.     print('$a, $b');
  13. }
复制代码
解构

当一个对象和一个模式相匹配,那么这个模式可以访问对象的数据,并可以把这个对象拆分成不同部分。换句话说,这个模式解构了这个对象。
代码样例:如下代码,List列表解构,和解构模式中的嵌套匹配模式。
  1. // 列表解构:`[a, b, c]`结构`numList`对象
  2. // 1. 匹配:`[a, b, c]`代表了具有3个元素的列表
  3. // 2. 拆分:列表的3个元素,分别赋值给了新的变量`a`、`b`和`cs`
  4. var numList = [1, 2, 3];
  5. var [a, b, c] = numList;
  6. print(a + b + c);
  7. // 列表模式:包含2个元素,且第1个元素是`a`或`b`,第2个元素赋值给变量`c`
  8. switch (list) {
  9.   case ['a' || 'b', var c]:
  10.     print(c);
  11. }
复制代码
模式的应用场景

在Dart语言总,有几个常见可以使用模式:

  • 局部变量的申明赋值
  • for和for-in循环语句。
  • if-case和switch-case语句。
  • 集合相关的控制流
变量申明

我们可以在Dart允许本地变量声明的任何地方使用模式变量声明,模式变量申明必须由var或者final + 模式组成(这也是Dart的模式变量的语法)。
代码样例:如下代码,使用模式,我们申明了a,b和c三个变量(并且完成赋值)。
  1. var (a, [b, c]) = ('str', [1, 2]);
复制代码
变量赋值

上小节变量申明的代码样例中,其实已经进行了模式变量赋值:首先进行模式匹配,然后解构对象,最终进行遍历赋值。
代码样例:如下代码,采用变量赋值模式,轻松进行了2个元素值交换,而无需使用第3个变量。
  1. var (a, b) = ('left', 'right');
  2. (b, a) = (a, b);
  3. print('$a $b');
复制代码
Switch和表达式模式

本文开头的样例其实已经提到,任何case的语句其实都包含了一个模式。在case中,可以应用任何的模式,变量赋值的作用域仅在Case语句内部。
Case模式可以匹配失败,它允许控制流:

  • 匹配并解构switch对象。
  • 匹配失败,则继续执行匹配。
  1. switch (obj) {
  2.   // 匹配:1 == obj
  3.   case 1:
  4.     print('one');
  5.   // 匹配:[first, last]区间
  6.   case >= first && <= last:
  7.     print('in range');
  8.   // 匹配:Record记录,包含2个字段
  9.   // 赋值:`a`和`b`局部变量(作用域:本Case内部)
  10.   case (var a, var b):
  11.     print('a = $a, b = $b');
  12.   default:
  13. }
  14. // 逻辑或模式:多个case共用
  15. var isPrimary = switch (color) {
  16.   Color.red || Color.yellow || Color.blue => true,
  17.   _ => false
  18. };
  19. switch (shape) {
  20.   case Square(size: var s) || Circle(size: var s) when s > 0:
  21.     print('Non-empty symmetric shape');
  22. }
复制代码
for和for-in循环模式

主要作用:迭代和解构集合。
代码样例:如下代码,for循环匹配模式,并解构和赋值给变量。
  1. Map<String, int> hist = {
  2.   'a': 23,
  3.   'b': 100,
  4. };
  5. // 匹配:`MapEntry`类型,继续匹配`key`和`value`命名字段子模式
  6. // 赋值:调用`key`和`value`的`getter`并赋值给`key`和`value`变量
  7. for (var MapEntry(key: key, value: count) in hist.entries) {
  8.   print('$key occurred $count times');
  9. }
  10. // 上诉代码的简写
  11. for (var MapEntry(:key, value: count) in hist.entries) {
  12.   print('$key occurred $count times');
  13. }
复制代码
其他场景模式

本文前面的章节,我们主要是展示Dart类型模式和解构,当然也包括(a, b)内容交换的例子。本章进一步学习其他的场景模式。
通过本章学习,主要解决我们几个问题:

  • 什么时候我们需要用到模式,我们为什么需要模式?
  • 模式主要解决什么类型的问题?
  • 什么样的模式最适合?
解构多个返回值

在之前的学习中,Record记录的用途之一就是聚合多个值,并让函数返回多个值。模式能匹配并解构Record记录,并赋值给局部变量。
代码样例:如下代码,userInfo(json)返回一个位置字段的记录,被解构并把位置值赋值给了name和age局部变量。
  1. // Record记录的使用
  2. var info = userInfo(json);
  3. var name = info.$1;
  4. var age = info.$2;
  5. // Record解构和赋值
  6. var (name, age) = userInfo(json);
复制代码
解构类实例

对象模式能匹配命名的对象类型,可以解构对象的数据,并调用对象属性的getters方法进行赋值。
代码样例:如下代码,命名类型Foo实例myFoo被解构并进行赋值给one和two变量。
  1. final Foo myFoo = Foo(one: 'one', two: 2);
  2. var Foo(:one, :two) = myFoo;
  3. print('one $one, two $two');
复制代码
代数数据类型

对象解构和Switch模式有助于编写代数数据类型风格代码,它比较适合以下几种场景:

  • 有一群相关联的类型。
  • 每个类型都有一个相同的操作,但这个操作对每个类型而言又有差异。
  • 我们希望把这个操作能一把实现,而不是把实现散落在每个类型中。
样例代码:如下代码,Shape是一个父类,2个或更多的子类都有计算面积的方法,最终通过calculateArea()函数一把实现了。
  1. sealed class Shape {}
  2. class Square implements Shape {
  3.   final double length;
  4.   Square(this.length);
  5. }
  6. class Circle implements Shape {
  7.   final double radius;
  8.   Circle(this.radius);
  9. }
  10. double calculateArea(Shape shape) => switch (shape) {
  11.       Square(length: var l) => l * l,
  12.       Circle(radius: var r) => math.pi * r * r
  13.     };
复制代码
校验JSON格式

前面章节,我们学习了List和Map类型的匹配和解构,它们也适用于JSON的key-value键值对。
代码样例:如下代码,在已知JSON格式的情况下,我们可以通过List和Map完成JSON的解构和赋值。
  1. var json = {
  2.   'user': ['Lily', 13]
  3. };
  4. var {'user': [name, age]} = json;
复制代码
但是,当JSON格式不明确的情况下,我们可以通过解构来校验JSON的格式。
代码样例:如下代码,我们通过case模式,完成了JSON数据的校验和赋值。
  1. if (json case {'user': [String name, int age]}) {
  2.   print('User $name is $age years old.');
  3. }
复制代码
如上代码,Case模式的匹配和赋值操作如下:

  • json是一个非空的map,进一步匹配map模式
  • json包含一个名为user的属性,且它是一个包含2个元素的list类型,list中2个元素类型分别为String和int。
  • 最终,list的2个元素分别赋值给了name和age局部变量。
我的本博客原地址:https://ntopic.cn/p/2023100401

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

金歌

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表