Dart 是 Flutter 的主要开发语言,Flutter 是一个来自 Google 用于移动应用开发的 SDK。本 codelab 将着重向你介绍 Java 开发人员可能并不了解的一些关于 Dart 的特性。

你可以在 1 分钟之内快速地写出 Dart 的函数,在 5 分钟内编写出脚本,在 10 分钟内快速编写出移动端的应用。

你将学习到

你将需要

你只需要一个浏览器就能完成这个 codelab !

你将在 DartPad 中编写和运行所有的示例代码,DartPad 是一个集成了 Dart 语言特性和核心代码库的一个交互式工具,它可以直接运行于浏览器中。当然,如果你喜欢的话,也可以使用别的 IDE 工具来进行编码,比如 WebStorm,提供了 Dart 插件的 IntelliJ 工具,或者是集成了 Dart Code extension 的 Visual Studio Code 编辑器。

为什么你需要学习这个 codelab ?

我想编写移动应用(Flutter) 我想编写 web 应用(AngularDart) 我想编写一些其它类型的 Dart 语言 我对 Dart 编程语言很感兴趣

你将从创建一个简单的 Bicycle 类来开始学习 Dart,这个类同在 Java 教程中所创建的类拥有同样的功能。 Bicycle 类包含了一些私有的成员变量,并对外暴露了 getter 和 setter 方法用来对成员变量进行操作。而在 main() 方法中会进行对 Bicycle 类的实例化操作并将其在控制台中打印出来。

启动 DartPad

这个 codelab 对每一组练习都提供了独立的实例。以下的链接会打开一个全新的默认包含 ‘Hello' 例子的 DartPad 实例。你可以在整个 codelab 的学习过程中一直使用同一个 DartPad,但一旦你点击了 Reset 按钮,DartPad 将会丢失你所有的工作而返回到默认状态。

DartPad 会在顶栏显示当前实例的状态,方便进行查看。

打开 DartPad

注意,DartPad 会对它的代码及时运行。

定义 Bicycle 类

main() 函数的上方,增加一个 Bicycle 类,它有三个实例变量,然后移除 main()函数中的内容,如下:

class Bicycle {
  int cadence;
  int speed;
  int gear;
}

void main() {
}

注意

定义一个 Bicycle 的构造函数

在 Bicycle 类中增加以下的构造函数:

Bicycle(this.cadence, this.speed, this.gear);

注意

Bicycle(int cadence, int speed, int gear) {
  this.cadence = cadence;
  this.speed = speed;
  this.gear = gear;
}

格式化代码

在任何时候都可以点击 DartPad 界面上的 Format 按钮来进行代码的格式化,此功能在向 DartPad 中粘贴代码时特别有用。

点击 Format 按钮

实例化并打印 bicycle

main() 方法中增加下列代码:

void main() {
  var bike = new Bicycle(2, 0, 1);
  print(bike);
}

移除可选的 new 关键字:

var bike = Bicycle(2, 0, 1);

注意

运行示例代码

点击 DartPad 上方的 Run 按钮来执行代码,如果 Run 按钮不可点击则代表有错误,可以通过 Problems 模块进行查看。

你将会看到下列这样的输出文本:

Instance of 'Bicycle'

注意

优化输出信息

刚刚得到的输出 "Instance of ‘Bicycle'" 虽然是正确的,但是并没有显示出更具体的信息。所有的 Dart 类中都有一个 toString() 方法,你可以复写这个方法来提供更具体的输出信息。

在 Bicycle 类中的任意位置增加 toString()方法:

@override
String toString() => 'Bicycle: $speed mph';

注意

运行实例代码

点击 Run

你将会看到如下输出:

Bicycle: 0 mph

遇到了问题?

检查一下代码

增加一个只读的变量

之前的 Java 示例定义了一个只读的变量 speed,并将其声明为 private 的,提供了一个 getter 方法。接下来,你将使用 Dart 来实现同样的方法。

在 DartPad 中打开 bicycle.dart (或者继续使用你当前的副本)

你可以在变量名前增加下划线 _ 来标记为它是私有的,也就是说可以仅仅通过改变变量名来实现将 speed 标记为只读的。

将 speed 标记为私有、只读的成员变量

在 Bicycle 构造函数中,移除 speed 参数:

Bicycle(this.cadence, this.gear);

在 main() 方法中,移除 Bicycle 构造函数调用中的第二个参数 (speed) :

var bike = Bicycle(2, 1);

将剩余使用到 speed 的地方将其更改为 _speed (有两个地方需要更改)

初始化 _speed 为 0:

int _speed = 0;

在 Bicycle 类中增加下列的 getter 方法

int get speed => _speed;

注意

完成将 speed 作为实例变量的代码

在 Bicycle 类中增加下列代码:

void applyBrake(int decrement) {
  _speed -= decrement;
}

void speedUp(int increment) {
  _speed += increment;
}

最终的 Dart 实例和一开始的 Java 版本看上去很像,但是 Dart 书写的代码只有 23 行,而 Java 的则有 40 行:

class Bicycle {
  int cadence;
  int _speed = 0;
  int get speed => _speed;
  int gear;

  Bicycle(this.cadence, this.gear);

  void applyBrake(int decrement) {
    _speed -= decrement;
  }

  void speedUp(int increment) {
    _speed += increment;
  }

  @override
  String toString() => 'Bicycle: $_speed mph';
}

void main() {
  var bike = Bicycle(2, 1);
  print(bike);
}

遇到了问题?

检查一下代码

The next exercise defines a Rectangle class, another example from the Java Tutorial.

接下来的练习是定义另外一个来自 Java 教程的 Rectangle 类

之前的 Java 代码中,使用了重载构造函数的方法,该方法在 Java 中很普遍,重载的构造函数和之前的构造函数具有相同的方法名,但是在方法的参数个数或者参数类型上有些许不同。Dart 并不支持构造函数的重载,而采用了另外一种方法来处理这种情况,我们将在本章接下来的地方看到它是如何实现的。

在 DartPad 中打开 Rectangle 示例

增加 Rectangle 构造方法

同之前的 Java 示例代码中不同,可以直接添加下列的一个没有方法体的构造方法来替代 Java 代码中的四个构造方法。

Rectangle({this.origin = const Point(0, 0), this.width = 0, this.height = 0});

这个构造方法使用了可选类型参数。

注意

优化文本输出

在 Rectangle 类中增加下列 toString() 方法:

@override
String toString() =>
      'Origin: (${origin.x}, ${origin.y}), width: $width, height: $height';

使用构造方法

使用下列方法来替代 main()方法,用来验证传递不同数量的参数来实例化 Rectangle 是否成功。

main() {
  print(Rectangle(origin: const Point(10, 20), width: 100, height: 200));
  print(Rectangle(origin: const Point(10, 10)));
  print(Rectangle(width: 200));
  print(Rectangle());
}

注意

Rectangle 的构造方法在 Dart 中只需要 1 行代码即可,而在 Java 中则需要 16 行代码。

运行示例代码

你将会看到如下的输出:

Origin: (10, 20), width: 100, height: 200
Origin: (10, 10), width: 0, height: 0
Origin: (0, 0), width: 200, height: 0
Origin: (0, 0), width: 0, height: 0

遇到了问题?

检查一下代码

在 Java 中工厂类是一个广泛使用的设计模式,它相比较直接对类进行实例化来说,具有诸多优势,比如隐藏实例化的具体细节,提供可以配置为其他类的能力,直接返回一个已有的对象而不是直接返回一个新的对象。

在该步骤中,将向你展示两种实现一个创建形状的工厂类的方法。

在这个练习中,你将会使用一个 Shapes 实例来实例化形状的类,并输出打印出他们的面积:

import 'dart:math';

abstract class Shape {
  num get area;
}

class Circle implements Shape {
  final num radius;
  Circle(this.radius);
  num get area => pi * pow(radius, 2);
}

class Square implements Shape {
  final num side;
  Square(this.side);
  num get area => pow(side, 2);
}

main() {
  final circle = Circle(2);
  final square = Square(2);
  print(circle.area);
  print(square.area);
}

在 DartPad 中打开 Shapes 例子

在命令行输出框中,你将会看到关于圆行和方形计算出的面积值:

12.566370614359172
4

注意

num get area => pi * pow(radius, 2); // Circle
num get area => pow(side, 2); // Square

选项 1:创建一个顶层的方法

在最外层作用域中(在所有类的作用域之外)实现一个工厂方法。

Shape shapeFactory(String type) {
  if (type == 'circle') return Circle(2);
  if (type == 'square') return Square(2);
  throw 'Can\'t create $type.';
}

将 main() 方法中前两行代码替换为下列代码来调用刚刚实现的工厂方法。

  final circle = shapeFactory('circle');
  final square = shapeFactory('square');

运行示例代码

命令行输出结果同之前的输出结果看起来一样。

注意

遇到了问题?

检查一下代码

选项2 :创建一个工厂模式的构造方法

使用 Dart 的 factory 关键字来创建一个工厂模式的构造方法

在抽象类 Shape 中增加一个工厂模式的构造方法

abstract class Shape {
  factory Shape(String type) {
    if (type == 'circle') return Circle(2);
    if (type == 'square') return Square(2);
    throw 'Can\'t create $type.';
  }
  num get area;
}

main() 方法中的前两行替换为下列的代码来实例化 Shape 类:

  final circle = Shape('circle');
  final square = Shape('square');

删除之前添加的 shapeFactory() 方法

注意

遇到了问题?

检查一下代码

Dart 语言并没有提供 interface 关键字,但是每一个类都隐式地定义了一个接口

在 DartPad 中打开 Shapes 示例代码(或者继续在你当前副本中使用)

扩展 Circle 类,增加一个 CircleMock:

class CircleMock implements Circle {}

你将会看到一个"Missing concrete implementations" 的错误,添加两个实例变量 arearadius 即可修复这个问题

class CircleMock implements Circle {
  num area;
  num radius;
}

注意

遇到了问题?

检查一下代码

在函数式编程中,你可以做到:

Dart 支持所有的这些特性,在 Dart 中,每个函数都是一个对象,并且每个函数都有它的类型 Function,这意味着所有函数都可以支持赋值操作,以及都可以作为参数传递给其他的函数。你可以将实例化 Dart 类当做一个函数的调用行为,类似于这个例子

下列代码使用了命令式编程的方法,并没有使用函数式:

String scream(int length) => "A${'a' * length}h!";

main() {
  final values = [1, 2, 3, 5, 10, 50];
  for (var length in values) {
    print(scream(length));
  }
}

在 DartPad 中打开 Scream 示例

你将会看到类似于下列的输出:

Aah!
Aaah!
Aaaah!
Aaaaaah!
Aaaaaaaaaaah!
Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah!

注意

将上述命令式代码转为函数式

将上述代码中的 main() 方法中的 for() {...} 替换为下面的一行代码,它直接使用了链式调用的方式:

  values.map(scream).forEach(print);

运行示例代码

该函数式的方法打印出来的结果同之前的结果一样。

遇到了问题?

检查一下代码

更多函数式的迭代特性

dart:collection 库中的 List 和 Iterable 支持 fold, where, join, skip 等函数式方法,另外 Dart 还支持 Map 和 Set 类型。

main() 方法中的 values.map() 一行代码替换为下列代码:

  values.skip(1).take(3).map(scream).forEach(print);

运行示例代码

你将会看到下列的输出:

Aaah!
Aaaah!
Aaaaaah!

注意

遇到了问题?

检查一下代码

在本篇 codelab 中,你了解到了 Java 和 Dart 中的一些差异。Dart 是很容易学习的,除此之外 Dart 的核心库和丰富的第三方代码库也会大大提高你的工作效率。Dart 在大型应用中表现优秀,在 Google 内部有几百个工程师将 Dart 运用于很多个优异的应用中,并持续地为 Google 创造价值。

接下来

短短的 20 分钟的 codelab 并不能像你完全展示出 Java 和 Dart 的区别。这个 codelab 并没有向你介绍:

如果你想学习更多 Dart 相关的运用,可以去尝试一下 Flutter codelab

想学习更多

你可以通过下列文章、资源、网站等学习更多跟 Dart 相关的知识

文章

资源

网站

感谢

感谢来自社区的 iCell 对本 Codelab 的翻译。

感谢反馈区 @TangSirOnGit, @Vadaski, @DanielFok99, @poel, @zejiang, 发现并报告错误。