本示例是商品与购物车,将其通用代码一步步改造成 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 的构造函数,我们有两点可优化:
- 以“参数名: 参数键值对”的方式指定调用参数,让调用者对参数更明确。
- 对于一个购物车,一定会有一个用户名,但不一定有优惠码,因此可提供一个不含优惠码的构造函数。
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
-------------------------