一个例子学习 Dart 语法特性

Flutter 基础

Posted by dks on July 17, 2019

本示例是商品与购物车,将其通用代码一步步改造成 Dart 风格代码,其中主要涉及 Dart 区别于其他语言的一些特性,用于巩固学习 Dart 语法特性。阅读本文之前,请保证已经了解 Dart基本语法

原始代码

首先这是一个不使用任何 Dart 语法特性的,一个有着基本功能的购物车。

//定义商品Item类
class Item {
  //成员变量:价格与名称
  double price;
  String name;

  //构造函数
  Item(name, price) {
    this.name = name;
    this.price = price;
  }
}

//定义购物车类
class ShoppingCart {
  //用户名称、创建时间、编号、商品列表
  String name;
  DateTime date;
  String code;
  List<Item> bookings;

  //获取购物车内所有商品的总价格
  price() {
    double sum = 0.0;
    for (var i in bookings) {
      sum += i.price;
    }
    return sum;
  }

  //创建购物车时,给创建时间赋值
  ShoppingCart(name, code) {
    this.name = name;
    this.code = code;
    this.date = DateTime.now();
  }

  //获取特定格式的购物车信息
  getInfo() {
    return '购物车信息:' +
        '\n-----------------------------' +
        '\n用户名: ' +
        name +
        '\n优惠码: ' +
        code +
        '\n总价: ' +
        price().toString() +
        '\n日期: ' +
        date.toString() +
        '\n-----------------------------';
  }
}

//运行代码,创建购物车,添加商品,然后打印出购物车信息
void main() {
  ShoppingCart sc = ShoppingCart('张三', '123456');
  sc.bookings = [Item('苹果', 10.0), Item('鸭梨', 20.0)];
  print(sc.getInfo());
}

如我们所愿,运行这段代码会打印以下信息:

购物车信息:
-----------------------------
用户名: 张三
优惠码: 123456
总价: 30.0
日期: 2019-07-16 17:33:52.114904
-----------------------------

在关于如何表达以及处理信息上,Dart 保持了既简单又简洁的风格。那接下来,我们就先从 表达信息入手,看看 Dart 是如何优化这段代码的。

类抽象改造

我们先来看看 Item 类与 ShoppingCart 类的初始化部分。它们在构造函数中的初始化工 作,仅仅是根据传入的参数进行属性赋值。 在 Dart 里,我们可以利用语法糖以及初始化列表,来简化这样的赋值过程,从而直接省去构造函数的函数体:

class Item {
  double price;
  String name;

  //删掉了构造函数函数体
  Item(this.name,this.price);
}

class ShoppingCart {
  String name;
  DateTime date;
  String code;
  List<Item> bookings;
	//冒号后是构造函数执行后执行的代码,跟之前的代码等效
  ShoppingCart(this.name, this.code) : date = DateTime.now();
}

接下来我们再来看两个类的属性和方法。可以注意到它们都有一个 name 属性,在Item 中标书商品名称,在 ShoppingCart 中表示用户名,同时Item类中有一个price属性,ShoppingCart 中有一个 price 方法,它们都表示当前价格。 考虑到name和price的名称与类型完全一致,我们可以抽象出一个基类 Meta,用于存放price和name属性。

class Meta {
  double price;
  String name;

  Meta(this.name, this.price);
}

class Item extends Meta {
  Item(name, price) : super(name, price);
}

class ShoppingCart extends Meta {
  DateTime date;
  String code;
  List<Item> bookings;

  //此处的 price 不同于 Item 的 price,用于计算总价格,而不是像 Item 中用于存储数据,
  //因此要改写 price 的 get 方法
  double get price {
    //...
  }

  ShoppingCart(name, this.code): date = DateTime.now(), super(name, 0);
}

方法改造

对于 price 方法,我们也可以使用 Dart 的特性来简化。怎么来简化呢,这里非常有意思。在 Dart 中,”+”运算符也是一个对象,因此我们可以重载 Item 类的”+”运算符,使其可以直接相加,可理解为合并商品为套餐商品,具体实现就是求价格的总和。

	//原 price 的 get 方法
  double get price {
    double sum = 0.0;
    for (var i in bookings) {
      sum += i.price;
    }
    return sum;
  }
//商品
class Item extends Meta {
  Item(name, price) : super(name, price);
  
  //重载"+"运算符,可理解为合并商品为套餐商品
  Item operator +(Item item) => Item(name + item.name, price + item.price);
}
class ShoppingCart extends Meta {
  DateTime date;
  String code;
  List<Item> bookings;

  //把迭代求和改写为归纳合并
  //reduce() 将数组中的每一个值与前面返回的值相加,最后返回相加的总和
  //在 Dart 中 方法也是对象,reduce 方法的入参就是一个 Function 对象,它实现了商品相加的具体运算
  double get price =>
      bookings.reduce((value, element) => value + element).price;
}

可以看到,代码简洁了很多。接下来看看 getInfo 方法如何优化。 在 getInfo 方法中,我们将 ShoppingCart 类的基本信息通过字符串拼接的方式,组合成特定格式的字符串。

  //使用三个单引号或者三个双引号可以创建多行字符串对象
  //可使用 $ 符直接引用对象
  getInfo() =>'''
  购物车信息:
  -------------------------
  用户名:$name
  优惠码:$code
  总价:$price
  创建日期:$date
  -------------------------
  ''';

去掉了转义字符和拼接代码,getInfo 更加清晰了。

对象初始化方式的优化

最后我们对 main 方法部分进行优化,在优化之前,必须先对方法参数的定义方式进行说明:

  • 方法可以有两种类型的参数:必需的和可选的。 必需的参数在参数列表前面, 可选参数在参数列表前面。
  • 可选参数又有两种声明方式,可选命名参数可选位置参数,两者区别在于调用方法时,区别参数是通过 参数名 区分还是根据 参数位置 区分。参看Functions
可选参数类型 命名参数 位置参数
定义方式 {Type paramName} [Type1 name1, Type2 name2]
调用方式 (paramName: value) (name1, name2)
定义示例 String say(String to, {String msg}){} String say([String msg, int volume]){}
调用示例 say(to:"Tom", msg:"hi") say("hi", 2)

原始 main 方法

void main() {
  ShoppingCart sc = ShoppingCart('张三', '123456');
  sc.bookings = [Item('苹果', 10.0), Item('鸭梨', 20.0)];
  print(sc.getInfo());
}

对于 ShoppingCart 的构造函数,我们有两点可优化:

  1. 以“参数名: 参数键值对”的方式指定调用参数,让调用者对参数更明确。
  2. 对于一个购物车,一定会有一个用户名,但不一定有优惠码,因此可提供一个不含优惠码的构造函数。
class ShoppingCart extends Meta {
	//...
  ShoppingCart(name) : this.withCode(name: name, code: null);

  //这种写法叫做命名构造函数,使用命名构造函数可以为一个类实现多个构造函数,或者使用命名构造函数来更清晰的表明你的意图
  ShoppingCart.withCode({name, this.code}): date = DateTime.now(), super(name, 0);

  //getInfo 中需要对优惠码进行兼容处理
  // a??b 运算符等同于其他语言中的(a!=null)?a:b
  getInfo() => '''
  购物车信息:
  -------------------------
  用户名:$name
  优惠码:${code??" 无 "}
  总价:$price
  创建日期:$date
  -------------------------
  ''';
}

调用相应的构造函数

void main() {
  ShoppingCart sc = ShoppingCart.withCode(name: '张三', code: '123456');
  sc.bookings = [Item('苹果', 10.0), Item('鸭梨', 20.0)];
  print(sc.getInfo());

  ShoppingCart sc2 = ShoppingCart.withCode(name: '张三');
  sc2.bookings = [Item('香蕉', 20.0), Item('西瓜', 40.0)];
  print(sc.getInfo());
}

接下来我们再看打印信息,它是直接通过 print 函数打印的,我们希望将打印信息的行为封装到 ShoppingCart 中。而且,打印信息是个通用功能,因此,我们将其封装成 PrintHelper 类。由于ShoppingCart 已经继承了 Meta 类,Dart不支持多继承,”混入”(mixin)派上用场了。

abstract class PrintHelper{
  printInfo() => print(getInfo);
  //定义抽象方法
  getInfo();
}
//使用关键词 with 来使用"混入",为类添加新功能
class ShoppingCart extends Meta with PrintHelper{

	//实现抽象方法
	getInfo(){
	//...
	}
}

此时,main 函数我们可以这样来写了

void main() {
	// .. 是级联操作符,可以在同一对象上连续调用多个函数,避免创建临时变量
  ShoppingCart.withCode(name: '张三', code: '123456')
  ..bookings= [Item('苹果', 10.0), Item('鸭梨', 20.0)]
  ..printInfo();

  ShoppingCart.withCode(name: '张三')
  ..bookings= [Item('香蕉', 20.0), Item('西瓜', 40.0)]
  ..printInfo();
}

完整代码

至此,我们已经将一段普通代码改造成了,简洁又强大的 Dart 风格代码。我们回顾一下完整代码,体会 Dart 的语法特性。

class Meta {
  double price;
  String name;

  Meta(this.name, this.price);
}
class Item extends Meta {
  Item(name, price) : super(name, price);

  Item operator +(Item item) => Item(name + item.name, price + item.price);
}

abstract class PrintHelper {
  printInfo() => print(getInfo());

  getInfo();
}
class ShoppingCart extends Meta with PrintHelper {
  DateTime date;
  String code;
  List<Item> bookings;

  double get price =>
      bookings.reduce((value, element) => value + element).price;

  ShoppingCart(name) : this.withCode(name: name, code: null);

  ShoppingCart.withCode({name, this.code})
      : date = DateTime.now(),
        super(name, 0);

  getInfo() => '''
  购物车信息:
  -------------------------
  用户名:$name
  优惠码:${code ?? " 无 "}
  总价:$price
  创建日期:$date
  -------------------------
  ''';
}
void main() {
  ShoppingCart.withCode(name: '张三', code: '123456')
    ..bookings = [Item('苹果', 10.0), Item('鸭梨', 20.0)]
    ..printInfo();

  ShoppingCart.withCode(name: '张三')
    ..bookings = [Item('香蕉', 20.0), Item('西瓜', 40.0)]
    ..printInfo();
}
  购物车信息:
  -------------------------
  用户名:张三
  优惠码:123456
  总价:30.0
  创建日期:2019-07-17 14:56:02.116505
  -------------------------
  
  购物车信息:
  -------------------------
  用户名:张三
  优惠码: 无 
  总价:60.0
  创建日期:2019-07-17 14:56:02.128194
  -------------------------