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 类的实例化操作并将其在控制台中打印出来。

ImageImage

启动 DartPad

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

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

Image 打开 DartPad

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

定义 Bicycle 类

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

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

void main() {
}

Image 注意

定义一个 Bicycle 的构造函数

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

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

Image 注意

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

格式化代码

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

Image点击 Format 按钮

实例化并打印 bicycle

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

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

Image 移除可选的 new 关键字:

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

Image 注意

运行示例代码

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

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

Instance of 'Bicycle'

Image 注意

优化输出信息

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

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

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

Image 注意

运行实例代码

Image 点击 Run

你将会看到如下输出:

Bicycle: 0 mph

遇到了问题?

检查一下代码

增加一个只读的变量

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

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

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

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

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

Bicycle(this.cadence, this.gear);

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

var bike = Bicycle(2, 1);

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

Image 初始化 _speed 为 0:

int _speed = 0;

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

int get speed => _speed;

Image 注意

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

Image 在 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 并不支持构造函数的重载,而采用了另外一种方法来处理这种情况,我们将在本章接下来的地方看到它是如何实现的。

Image 在 DartPad 中打开 Rectangle 示例

增加 Rectangle 构造方法

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

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

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

Image 注意

优化文本输出

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

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

使用构造方法

Image 使用下列方法来替代 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());
}

Image 注意

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);
}

Image 在 DartPad 中打开 Shapes 例子

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

12.566370614359172
4

Image 注意

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

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

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

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

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

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

运行示例代码

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

Image 注意

遇到了问题?

检查一下代码

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

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

Image 在抽象类 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;
}

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

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

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

Image 注意

遇到了问题?

检查一下代码

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

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

Image 扩展 Circle 类,增加一个 CircleMock:

class CircleMock implements Circle {}

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

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

Image 注意

遇到了问题?

检查一下代码

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

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));
  }
}

Image 在 DartPad 中打开 Scream 示例

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

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

Image 注意

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

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

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

运行示例代码

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

遇到了问题?

检查一下代码

更多函数式的迭代特性

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

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

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

运行示例代码

你将会看到下列的输出:

Aaah!
Aaaah!
Aaaaaah!

Image 注意

遇到了问题?

检查一下代码

在本篇 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, 发现并报告错误。