skip to content
usubeni fantasy logo Usubeni Fantasy

Dart | class 详解

/ 10 min read

dart 是 Flutter 的基础,其中类(class)更是尤为重要,可以说是 Flutter 入门必备。这篇文章会跟着官方文档整理一下类的用法与需要注意的点。(因为我的本业是前端,所以其中会经常提到 JavaScript 并与其作对比)

概述

Dart 也是一种面向对象的语言。每个对象都是一个类的实例,所有类都派生于 Object 类。另外 Dart 有一个特点 —— mixin,类体可以在多个类中重用。

类的使用

对象“成员”包括方法(函数)和实例变量(数据),你可以通过点(.)访问他们。

var p = Point(2, 2);
// Set the value of the instance variable y.
p.y = 3;
// Get the value of y.
assert(p.y == 3);
// Invoke distanceTo() on p.
num distance = p.distanceTo(Point(4, 4));

另外,使用 ?. 代替 . 可以避免操作对象为 null 的情况。

// js 也经常有这种问题
if (p) {
p.y = 4;
}

相比之下,dart 可以这样:

// 即使 p 是 null 也不会报错
p?.y = 4;

你可以使用类构造器创建对象。构造器名称可以是 class 名称本身,也可以自己取名,所以格式为 ClassNameClassName.identifier

var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});

如果你奇怪为什么实例化没有 new(毕竟在 js 里 new 是必须有的实例化标志),因为 Dart 2 中 new 关键字可写可不写。你可以根据习惯加上,这并不是错误的,以下代码效果与上面相同。

var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});

从实例获取它的类的方法:

print('The type of a is ${a.runtimeType}');

编写构造器

与类名同名的函数即是构造器。this 指向当前实例,在变量名不冲突时可以忽略。

class Point {
num x, y;
Point(num x, num y) {
// There's a better way to do this, stay tuned.
this.x = x;
this.y = y;
}
}
// 不写 this
class Point {
num xx, yy;
Point(num x, num y) {
// There's a better way to do this, stay tuned.
xx = x;
yy = y;
}
}

另外因为把构造器的参数传入到实例变量这个操作太常见了,所以有这么一个语法糖:

class Point {
num x, y;
// 设置 x 和 y 的语法糖
// 这会在构造函数体 运行前 进行设置
Point(this.x, this.y);
}

具名构造函数

使用方法上面已经说过了

class Point {
num x, y;
Point(this.x, this.y);
// 具名构造函数
Point.origin() {
x = 0;
y = 0;
}
}

记住构造器是不会被继承的,具名构造器不会被子类继承。如果你需要在子类使用父类的具名构造器,你需要在子类手动执行那个构造器。

默认构造函数

我觉得这是一个比较难理解的点,默认构造函数当你没有声明任何构造函数(包括具名构造函数)时会默认添加。这个构造函数有两个特点:第一,无参数;第二,不具名。同时,它会调用父类的默认构造函数。

初始化列表

除了引用父类的构造器,在自身构造函数体运行前,还可以使用初始化列表。使用初始化列表用冒号分开。另外,初始化时 rhs 不能访问 this。

Point.fromJson(Map<String, num> json)
: x = json['x'],
y = json['y'] {
print('In Point.fromJson(): ($x, $y)');
}

重定向构造器

有时候一些构造器功能与同 class 的其他构造器是一样的,这个时候可以直接把其他构造器重定向到功能一样的构造器上。重定向构造器的函数体为空,与重定向目标之间也是以冒号分隔。

class Point {
num x, y;
// The main constructor for this class.
Point(this.x, this.y);
// Delegates to the main constructor.
Point.alongXAxis(num x) : this(x, 0);
}

定值构造器

如果你的 class 生成的是定值,可以使用这个对象编译时定值。(但这不是必定生成定值)要得到这么一个 class 需要把所有实例变量定义为 final

class ImmutablePoint {
static final ImmutablePoint origin =
const ImmutablePoint(0, 0);
final num x, y;
const ImmutablePoint(this.x, this.y);
}

工厂构造器

注意:工厂方法是一种设计模式,而不是 Dart 的专有名词。

class Logger {
final String name;
bool mute = false;
// _cache is library-private, thanks to
// the _ in front of its name.
static final Map<String, Logger> _cache =
<String, Logger>{};
factory Logger(String name) {
if (_cache.containsKey(name)) {
return _cache[name];
} else {
final logger = Logger._internal(name);
_cache[name] = logger;
return logger;
}
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) print(msg);
}
}

extends 时的默认行为

子类不继承父函数的构造函数,子类只会调用父类的无参数构造函数。

父类构造器在子构造器函数体最前面调用,如果初始化列表也存在,会优先运行:

  1. 初始化列表
  2. 父类无参数构造器
  3. 自己的无参数构造器

当父类构造函数有参数时,子类 extends 直接报错。

调 extends 时用非默认构造器

既然默认只会调用父类的默认无参数构造函数,需要调用其他函数就需要自己动手了。

class Person {
String firstName;
Person.fromJson(Map data) {
print('in Person');
}
}
class Employee extends Person {
// Person 因为声明了具名函数所以没有默认函数
// 你必须手动调用 super.fromJson(data)
Employee.fromJson(Map data) : super.fromJson(data) {
print('in Employee');
}
}
main() {
var emp = new Employee.fromJson({});
// Prints:
// in Person
// in Employee
if (emp is Person) {
// Type check
emp.firstName = 'Bob';
}
(emp as Person).firstName = 'Bob';
}

Getter 与 Setter

class Rectangle {
num left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
// Define two calculated properties: right and bottom.
num get right => left + width;
set right(num value) => left = value - width;
num get bottom => top + height;
set bottom(num value) => top = value - height;
}
void main() {
var rect = Rectangle(3, 4, 20, 15);
assert(rect.left == 3);
rect.right = 12;
assert(rect.left == -8);
}

抽象类

使用 abstract 定义一个抽象类,抽象类不能被实例化,它作为接口定义非常有用。抽象类里有抽象方法,以 ; 代替函数体。

// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
// Define constructors, fields, methods...
void updateChildren(); // Abstract method.
}

隐性接口(Implicit interfaces)

**在 Dart 中每一个 class 都是一个隐性 interface。**如果你单纯想借用 B 的 API,可以让 A implements B 接口。模拟 Java 的接口可以使用 Dart 抽象类。

// A person. The implicit interface contains greet().
class Person {
// In the interface, but visible only in this library.
final _name;
// Not in the interface, since this is a constructor.
Person(this._name);
// In the interface.
String greet(String who) => 'Hello, $who. I am $_name.';
}
// An implementation of the Person interface.
class Impostor implements Person {
get _name => '';
String greet(String who) => 'Hi $who. Do you know who I am?';
}
String greetBob(Person person) => person.greet('Bob');
void main() {
print(greetBob(Person('Kathy')));
print(greetBob(Impostor()));
}

枚举类

枚举是一种特殊的类,使用 enum 关键字声明一个枚举类型:

enum Color { red, green, blue }
// enum 的每个值都有一个 index getter 返回值(以 0 开始)的位置
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
// 可以使用 values 访问 enum 所有值
List<Color> colors = Color.values;
assert(colors[2] == Color.blue);

另外,枚举有两个限制:

  • 不能继承、mix in 或 implement 枚举
  • 不能显式实例化枚举

静态变量和静态方法

使用 static 关键字实现静态变量和静态方法。

静态变量在使用时才会被初始化。

class Queue {
static const initialCapacity = 16;
// ···
}
void main() {
assert(Queue.initialCapacity == 16);
}

静态变量不在实例上进行操作,所以不能访问 this

import 'dart:math';
class Point {
num x, y;
Point(this.x, this.y);
static num distanceBetween(Point a, Point b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
}
void main() {
var a = Point(2, 2);
var b = Point(4, 4);
var distance = Point.distanceBetween(a, b);
assert(2.8 < distance && distance < 2.9);
print(distance);
}

完结

篇幅不短,谢谢你耐心看完!若有其他遗漏或没讲清楚的 class 重点,请在评论区提醒一下。

参考文献

评论组件加载中……