Dart语言一日游

2,022 阅读3分钟

Dart语言一日游

前言

Dart是谷歌Flutter框架使用的语言。Dart语言目前还是个冷门语言,但是它很好上手,对于有编程基础的人来说,一天时间足够掌握了。本文就是结合我自己的学习过程,给大家总结一下经验,帮助大家快速上手。

学习Dart语言,这一篇文章就够用了。

本文包括哪些知识点

  • 如何创建一个类和它的构造器
  • 如何定义类的变量
  • 如何创建getter和setter
  • Dart对于变量私有公有访问的控制
  • 如何使用工厂模式创建一个类实例
  • 类的继承机制
  • Dart函数式编程

需要准备的东西

因为本文只是学习Dart语言,只需要一个能科学上网的浏览器就够用了。

Dart提供了在线的IDE开发环境DartPad,极大的方便了我们的学习。

万物之源:类和对象

对于一门面向对象的程序语言,类和对象永远都是最重要的组成部分。所以我们首先来看如何创建一个Dart类。

打开DartPad,发现已经帮你写好了一个HelloWorld程序了。其中main函数就是入口主函数。这个函数循环输出四行字符串。

void main() {
  for (var i = 0; i < 4; i++) {
    print('hello $i');
  }
}

类的创建

下面我们来创建一个类,我们用自行车来距离。创建一个Bicycle类。代码如下:

class Bicycle{
  int speed;
  String desc;
}

void main() {
}

怎么样,是不是非常熟悉?(注意,这里每行结尾还是要写分号,略蛋疼)

这个类有两个属性,速度和描述。如何创建一个实例呢?也很简单,这样就好了。

class Bicycle{
  int speed;
  String desc;
}

void main() {
  var bike = Bicycle();
  bike.speed = 20;
  bike.desc = "我的自行车";
  print(bike);
}

点一下页面上的run按钮,输出结果:

Instance of 'Bicycle'

这里有两个知识点:

  1. 我们用var关键字创建了一个bike实例并且输出。如果想要创建一个不可变的变量,用关键字final。(这个final就是kotlin的val)
  2. 类的属性都没有public或者private关键字。没错,Dart没有publicprivate关键字,类里面声明的属性默认都是公共的。这个要注意一下。如何声明私有变量后面再讲。

构造函数

能不能像我们习惯的那样写个构造函数传参呢?当然可以,这样写就行了:

class Bicycle {
  int speed;
  String desc;

  Bicycle(this.speed, this.desc);
}

void main() {
  var bike = Bicycle(20, "我的自行车");
  print(bike);
}

// 上面的简便构造函数写法与下面的等价:
Bicycle(int speed, String desc) {
  this.speed = speed;
  this.desc = desc;
}

run一下,输出和刚才还是一样的

Instance of 'Bicycle'

这个输出不太友好,我们来改进一下。同Java一样,每个类有自己的toString()函数,我们覆写它就可以。

class Bicycle {
  int speed;
  String desc;

  Bicycle(this.speed, this.desc);

  @override
  String toString() {
    return '$desc速度: $speed 公里/小时';
  }
  
  // 简便写法
  @override
  String toString() => '$desc速度: $speed 公里/小时';
}

注意,函数前面不用写fun关键字。简便的写法中,用=>符号来确定返回值。最后的输出为:

我的自行车速度: 20 公里/小时

好了下面我们来介绍一下如何声明一个私有的变量。因为没有private关键字,所以Dart用了一个比较蛋疼的处理,就是变量名前面加下划线,你没看错,是下划线。。。所以我们把desc变量声明为_desc,这样就把它私有化了。

class Bicycle {
  int speed;
  String _desc;

  Bicycle(int speed) {
    this.speed = speed;
    this._desc = "我的自行车";
  }

  @override
  String toString() {
    return '$_desc速度: $speed 公里/小时';
  }
}

void main() {
  var bike = Bicycle(20);
  print(bike);
}

这样修改后,在main函数中就无法访问desc变量了,不能set也不能get。

那么问题又来了,如果我想创建一个只读的变量呢?能get但是不能set。这个就需要在私有变量的基础上,自己提供一个get函数了。写法是这样的

class Bicycle {
  int speed;
  String _desc;

  // 为私有变量提供get函数
  String get desc => _desc;

  Bicycle(int speed) {
    this.speed = speed;
    this._desc = "我的自行车";
  }

  @override
  String toString() {
    return '$_desc速度: $speed 公里/小时';
  }
}

void main() {
  var bike = Bicycle(20);
  print(bike.desc);
  print(bike);
}

最后介绍一下为一个类添加函数,这个非常简单,看一下就好:

class Bicycle {
  int speed;
  String _desc;
  
  void speedUp(int value) {
    this.speed += value;
  }

  int getSpeed() {
    return this.speed;
  }
}

OK,类和对象这个部分已经完成了。

可选参数

Dart和Kotlin一样,支持函数设置可选参数,避免Java那种累赘的重载。这里我们用这个例子RectangleExample

为这个类加入一个可选参数的构造函数,最后再加一个toSting。

class Rectangle {
  int width;
  int height;
  Point origin;

  Rectangle({Point origin = const Point(0, 0), int width, int height}) {
    this.origin = origin;
    this.width = width;
    this.height = height;
  }
  
  @override
  String toString() =>
      'Origin: (${origin.x}, ${origin.y}), width: $width, height: $height';
}

这里坐标orgin设置了默认参数。注意,使用默认参数时,需要将参数包裹在{ }中。之后再main函数里进行调用:

void 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());
  
  // 注意:这样写会报错,需要加参数名
  print(Rectangle(100, 100));
}

注意,这里需要填写参数名称,不能直接给值。这一点上相比于Kotlin确实要麻烦一些。

最后控制台的输出是这样的:

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

使用工厂模式

使用工厂模式创建类的实例是开发中常用的情况。Dart对此还特意提供了一个factory关键字。下面我们来介绍一下如何在Dart中快速实现工厂模式。先看这个例子:

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

这里有几个知识点:

  • 第一行的import,说明Dart提供了数学运算等一系列基础库,例如 dart:core,dart:async,dart:convert,dart:collection,这个后面可以自己去看API详细了解
  • 和Kotlin一样,一个文件可以定义多个类
  • Dart也是支持抽象类的,继承的时候,使用implements关键字。Dart中没有interface接口的概念,这个后面再细讲。

下面针对父类Shape加入一个工厂构造函数,使用关键字factory,并且在main函数中调用,代码如下:

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

void main() {
  try {
    print(Shape('circle').area);
    print(Shape('square').area);
    print(Shape('triangle').area);
  } catch (err) {
    print(err);
  }
}

输出如下:

12.566370614359172
4
Can't create triangle.

观察这段代码,里面引入了异常处理机制。使用工厂函数创建的时候,如果传入的类型不合法,会throw一个异常。然后在main函数中使用try/catch函数来捕获这个异常并处理。另外,因为示例代码中用单引号包裹字符串,所以异常文案中的Can't creat用到了转义字符,用双引号包裹字符串也是可以的。

当然除了上面介绍的使用factory关键字创建工厂构造函数之外,你也可以像Kotlin一样,自己写一个工厂函数,例如:

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

这个效果是一样的,觉得那种顺手就用哪一种吧。 示例代码在这里

Interface接口问题

前面说了,Dart没有Java里面Interface的概念。为什么呢?我来告诉你为什么?你说为什么?!

额,抱歉,写了这长了,偶尔皮一下。

实际上是因为每一个Dart类都隐形的有一个接口。到底什么意思呢?来看看刚才的例子

在其中加入一个CircleImpl类继承Circle:

class CircleImpl implements Circle {}

运行发现会报错:

Error: The non-abstract class 'CircleImpl' is missing implementations for these members:
 - 'radius'
 - 'area'

这是因为Circle中含有radius和area两个属性,虽然Circle类中已经实现了这两个参数的处理,但是CircleImpl类并没有继承到具体实现。

所以Dart的继承并不是真正意义上的继承那个类,而是继承这个类对应的数据结构的接口,这个数据结构包括它的参数和函数,但是不包括实现。这也是为什么Dart继承的关键字是implement而不是extends

CircleImpl类继承的不是Circle,只是继承了它的数据结构对应的接口,只是告诉你需要有radius和area两个属性,但是没有把实现继承给你。

所以我们把CircleImpl类改成这样就好了:

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

可以自己尝试写一个带函数的类,然后写个子类继承它,看看是什么效果?

函数式编程

函数式编程对于熟练使用Kotlin和Swift的人应该都不陌生了,Dart也是有相应的特性的。

下面我们来介绍一下,首先是函数的返回可以直接是一个表达式,这个前面都见过:

// 函数式风格写法
String sayHello() => "Hello World";

// 等价于传统返回值写法
String sayHello() {
  return "Hello World";
}

再来看看映射操作、集合遍历和将函数作为参数的特性:

String sayHello(int time) => "Hello World! $time";

main() {
  final values = [1, 2, 3, 4, 5, 6];
  values.map(sayHello).forEach(print);
}

// 输出结果
Hello World! 1
Hello World! 2
Hello World! 3
Hello World! 4
Hello World! 5
Hello World! 6

这里首先用到了map映射,values是一个int数组,用map映射,将int通过函数sayHello变成了一个字符串数组。之后再用集合遍历forEach,遍历的调用print函数打印结果。这里map和forEach都是将一个函数当做参数接收了。

此外Dart集合框架中支持map和set数据结构,而且提供了类似Kotlin的那些便捷函数,例如下面这个写法,就是跳过第一个元素,之后再取前三个元素进行处理:

main() {
  final values = [1, 2, 3, 4, 5, 6];
  values.skip(1).take(3).map(sayHello).forEach(print);
}

// 输出结果
Hello World! 2
Hello World! 3
Hello World! 4

最后说说控制流,为什么写到最后还不讲讲控制流呢,因为Dart的控制流和Java真的一模一样,什么if/else啊,switch啊都一样,除了没有when语句。这个随便写写就知道了。

写在最后

恭喜你已经入坑了。读到这里你已经掌握了Dart语言的全部基础知识!怎么样不难吧,虽然Dart冷门了点,但是技多不压身嘛。

详细的类库API可以查阅Dart语言官网。这里写的非常详细。就像当年刚学Java的时候看JavaAPI一样使用它就行了。

后面我会再写写Flutter的游玩感受。这个东西我个人认为目前就是调研调研,简单了解一下就好,没必要深入学习。其实Dart语言看完这篇文章就够用了,太细节的类库API用到的时候查一下就好了。

ShadowJoker,2019.03.17