Dart | class 详解

March 02, 2019codingdart

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 重点,请在评论区提醒一下。

参考文献


暂时没有留言,要抢沙发吗?
留言(不再受实现原理所限,立即更新!回复功能现在可用,但邮箱提醒未完成)
文章留言