Dart 语言入门 (四) - Classes

Dart 语言系列

Dart 语言入门 (一)

Dart 语言入门 (二)

Dart 语言入门 (三)

Dart 语言入门 (四)

Dart 语言入门 (五)

前言

这一章不按照官方文档的顺序来,先介绍一下 Classes.

Classes

Dart 是一个面向对象的语言,具有类和 mixin-based 继承。 每一个对象都是一个类的实例,所有的类都是继承自 Object. Mixin-based 继承意味着虽然每一个类有一个超类(除了对象),但是类体可以在多个类的层次结构中重用。

Using class members

对象包含函数和数据成员变量(分别包含方法和实例变量).
当你调用一个方法,你可以调用一个对象:这个方法可以访问对象的方法和数据。
使用 点. 可以引用实例变量的或者方法:

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:

// If p is non-null, set its y value to 4.
p?.y = 4;

Using constructors

你可以使用构造函数创建一个对象。构造名字要么是 ClassName 或者 ClassName.identifier.例如:下面的代码创建 Point 对象 使用 Point()Point.fromJson() 构造函数:

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

下面的代码有同样的功能,但是使用可选的 new关键字在构造名字之前:

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

Version note: new 关键字在 Dart 2 中变成可选的.

一些类提供了 constant constructors. 使用常量构造来创建一个编译期常量,在构造函数名字前面设置关键字 const:

var p = const ImmutablePoint(2, 2);

创建两个相同的编译期常量会产生一个单独,标准的常量:

var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a, b)); // They are the same instance!

(注:这个跟一般编程语言中的单例的实现类似,不过需要注意的一点是:获取该对象的时候前面需要加上const 。)

在一个常量的上下文,你可以省略const 在一个构造函数或者文字之前。例如:看这个代码,创建了一个const map:

// Lots of const keywords here.
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};

你可以省略除了第一个使用的const关键字之外的所有的:

// Only one const, which establishes the constant context.
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

如果常量构造函数在常量上下文之外并且在没有const它的情况下调用, 它会创建一个 非常量对象(non-constant object):

var a = const ImmutablePoint(1, 1); // Creates a constant
var b = ImmutablePoint(1, 1); // Does NOT create a constant

assert(!identical(a, b)); // NOT the same instance!

Version note: const 关键爱你变成可选的在常量上下文中 在Dart 2.

Getting an object’s type (获取一个对象的类型)

在运行时为了获取到一个类型,你可以使用对象的 runtimeType属性,将会返回 Type 对象.

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

到这里你已经明白如何使用 classes. 剩余的部分展示如何实现classes.

Instance variables

这里是如何声明一个实例变量:

class Point {
  num x; // Declare instance variable x, initially null.
  num y; // Declare y, initially null.
  num z = 0; // Declare z, initially 0.
}

所有未初始化的实例变量拥有值 null.

所有的实例变量会生成一个隐式的 getter方法。Non-final 实例变量也会生成一个隐式的 setter 方法。更多细节,查看 Getters and setters

class Point {
  num x;
  num y;
}

void main() {
  var point = Point();
  point.x = 4; // Use the setter method for x.
  assert(point.x == 4); // Use the getter method for x.
  assert(point.y == null); // Values default to null.
}

如果初始化声明它的实例变量(而不是构造函数或方法),则在创建实例时该变量会被赋值,是在该实例在构造函数及其初始化列表执行之前。

Constructors

声明一个构造器通过创建一个和类名相同的函数(另外,还有一个额外的标识符被描述在 Named constructors. 最通常的构造函数,是生成构造函数,创建一个类的新的实例:

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 关键字引用着当前实例。

**Note:使用 this 仅仅在在有命名冲突的时。相反,Dart 省略了 this **

赋值构造器参数给实例变量的形式是相同的,Dart 有语法糖使初始化更加简单:

class Point {
  num x, y;

  // Syntactic sugar for setting x and y
  // before the constructor body runs.
  Point(this.x, this.y);
}

Default constructors

如果你没有生成一个构造函数,将会提供一个默认的构造函数。默认的构造函数没有参数并且在超类中会触发无参的构造函数。

Constructors aren’t inherited (构造函数不能继承)

子类是不能够继承构造函数从它的超类中。没有声明构造函数的子类拥有默认(无参,没有名字)构造器。

Named constructors

使用命名的构造函数实现一个类的多个构造函数,或者提供额外的声明:

class Point {
  num x, y;

  Point(this.x, this.y);

  // Named constructor
  Point.origin() {
    x = 0;
    y = 0;
  }
}

记住构造函数不能继承,意味着超类的命名构造函数不能够被子类继承。如果你想要子类被创建的时候拥有一个定义在超类的命名构造函数,你必须一个在子类的构造函数。

Invoking a non-default superclass constructor

默认的,子类的构造函数会调用超类中无名的,没有参数的构造函数。
超类的构造函数会被在构造体开始之前被调用。如果使用了初始化列表(initializer list,在超类被调用之前会被执行。通常,执行顺序如下;

  • 1.initializer list
  • 2.superclass’s no-arg constructor
  • 3.main class’s no-arg constructor

如果超类没有无名,无参的构造函数,你必须手动的去调用超类中一个构造函数。在冒号(:)之后,在构造函数体(如果有)之前指定超类构造函数。

在下面的例子,Employee类调用其超类Person的命名构造函数

class Person {
  String firstName;

  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  // Person does not have a default constructor;
  // you must call super.fromJson(data).
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

main() {
  var emp = new Employee.fromJson({});

  // Prints:
  // in Person
  // in Employee
}

因为在调用构造函数之前会计算超类构造函数的参数,所以参数可以是一个表达式,例如函数调用:

class Employee extends Person {
  Employee() : super.fromJson(getDefaultData());
  // ···
}

** Warning:超类的构造函数的参数是不能够访问 this.例如: 参数可以被静态方法调用但是不能是实例方法.**

Initializer list

除了调用父类的构造函数,你也可以初始化实例变量在构造函数体开始之前。使用冒号分割初始化。

// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map<String, num> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x, $y)');
}

Warning:初始化函数的右侧是不能够访问 this .

在开发中,你可以使用 assert 来验证输入的正确性 在初始化列表中.

Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}

当设置 final作用域时,初始化列表是很方便的。下面的例子在初始化列表中初始了三个final 作用域。

import 'dart:math';

class Point {
  final num x;
  final num y;
  final num distanceFromOrigin;

  Point(x, y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}

main() {
  var p = new Point(3, 4);
  print(p.distanceFromOrigin);
}
//prints 5

Redirecting constructors

有时候构造函数的目的仅仅是在同一个类中重定向到另一个构造函数中。一个重定向的构造函数体是空的,构造函数调用出现在冒号之后。

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

Constant constructors

如果你有的类所创建的对象永远不会改变,你可以使这些对象成为编译期常量.为了做到这个,定义一个 const 构造函数 并且保证所有的实例变量是 final.

class ImmutablePoint {
  static final ImmutablePoint origin =
      const ImmutablePoint(0, 0);

  final num x, y;

  const ImmutablePoint(this.x, this.y);
}

常量构造函数不是总是创建常量.

Factory constructors

使用 factory 关键字 当实现一个构造函数,这个构造函数不是总创建新的实例。例如,工厂构造函数构造函数可能返回一个从缓存中的实例,或者返回实例的子类型。

下面的例子证明了一个工厂构造函数从缓存中返回一个对象:

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

NOTE:工厂构造函数不能访问 this.

调用一个工厂构造函数和你调用其他构造函数一样:

var logger = Logger("UI");
logger.log('Button click');

Methods

Methods 是为对象提供行为的函数。

Instance methods

对象的实例方法可以访问实例变量和this. distanceTo ()方法在下面的样例中是实例方法的例子:

import 'dart:math';

class Point {
  num x, y;

  Point(this.x, this.y);

  num distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return sqrt(dx * dx + dy * dy);
  }
}

Getters and setters

Getterssetters 为对象提供了读和写属性.每一个实例都包含一个隐式的gettersetter. 如果通过实现 getterssetter 来创建一个额外的属性,使用 getset 关键字:

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

使用 getterssetters,你可以从实例变量开始,然后使用方法包裹它们,这一切是不需要改变客户端代码的。

Note:无论getter是否明确定义,例如自增操作符(++)会如期运行。为了避免不可以预料的副作用,操作符只需调用一次getter,将其值保存在临时变量中

Abstract methods

实例,gettersetter 方法可以抽象,定义一个接口而把实现留给其他类。抽象方法仅仅存在 abstract classes

为了使方法抽象,使用分号(;) 来代替方法体:

abstract class Doer {
  // Define instance variables and methods...

  void doSomething(); // Define an abstract method.
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // Provide an implementation, so the method is not abstract here...
  }
}

Abstract classes

使用 abstract修饰符来定义一个抽象类- 这个类是不可以被初始化的。抽象类对于定义接口是有用的,通常还有一些实现。如果你想你的抽象类可以被实例化,定义一个 factory constructor.
抽象类通常拥有 abstract methods。这里是抽象类包含抽象方法的例子:

// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
  // Define constructors, fields, methods...

  void updateChildren(); // Abstract method.
}

Implicit interfaces

每个类都隐式定义一个接口,该接口包含该类的所有实例成员及其实现的任何接口.你如果想要创建的类A支持类B的API但是并没有继承B类的实现,类A应该实现类B的接口。
一个类实现一个或者多个接口统统声明它们在 implements 条款中,并且提供接口所需要的APIs.例如:

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

这里有指明一个类实现多个接口的例子:

class Point implements Comparable, Location {...}

Extending a class

使用 extends 来创建一个子类,并且super 来引用 超类:

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ···
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ···
}

Overriding members

子类可以覆盖实例方法,getter,setter. 你可以使用@override 标注只能你想要覆盖的成员:

class SmartTelevision extends Television {
  @override
  void turnOn() {...}
  // ···
}

为了抽象一个方法参数的类型或者实例变量在代码中是 type safe ,你可以使用
covariant keyword.

Overridable operators

你可以覆盖下面表中的运算符. 例如 : 定义 Vector 类,你可能定义一个 + 方法来相加两个vectors.

Note:你可能要注意 != 是不能覆盖的操作符. 表达式e1 != e2 仅仅是 !(e1 == e2) 的语法糖.

这里有一个覆盖 +- 操作符的例子:

class Vector {
  final int x, y;

  Vector(this.x, this.y);

  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);

  // Operator == and hashCode not shown. For details, see note below.
  // ···
}

void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);

  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}

如果你想到覆盖 == ,你应该覆盖对象hashCode getter 方法. 覆盖 ==hasCode,可以参考 Implementing map keys

noSuchMethod()

为了检测或者响应代码是否企图使用不存在的方法或者实体变量,你可以重写 noSuchMethod()

class A {
  // Unless you override noSuchMethod, using a
  // non-existent member results in a NoSuchMethodError.
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: ' +
        '${invocation.memberName}');
  }
}

你不能调用一个未实现的方法除非下列中的一个条件成立:

  • 接收者拥有一个静态类型 dynamic
  • 接收者拥有一个静态类型,这个类型定义了未实现的方法(abstract 是可以的),并且接收者的动态类型实现了noSuchMethod
    ,这不同于类中的对象.
    更多信息,可以参照非正式的 noSuchMethod forwarding specification.

Enumerated types

枚举类型,常常被称作 enumerations 或者 enums,是一种特殊类型的类,用来表示固定数量的常量值.

Using enums

声明一个枚举类型使用 enum 关键字:

enum Color { red, green, blue }

每一个值在枚举中拥有一个 index getter, 将会返回一个基于零位置的值在枚举声明中. 例如: 第一个值索引是 0,第二值的索引是 1.

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

为了得到枚举中所有的值的列表,使用枚举的 values 常量.

List<Color> colors = Color.values;
assert(colors[2] == Color.blue);

你可以在 switch 语句中使用枚举, 如果你没有处理枚举的值将会得到警告:

var aColor = Color.blue;

switch (aColor) {
  case Color.red:
    print('Red as roses!');
    break;
  case Color.green:
    print('Green as grass!');
    break;
  default: // Without this, you see a WARNING.
    print(aColor); // 'Color.blue'
}

枚举类型有以下的限制:

  • 您不能子类化,混合或实现枚举。
  • 您无法显式实例化枚举。

更多信息,参考 Dart Language Specification.

Adding features to a class: mixins

Mixins 可以多个类的层级中重复的使用类的代码.

要使用 mixin,使用 wtih 关键字后面跟上一个或者多个mixin 名字.
下面的例子展示了两个类使用 minxins

class Musician extends Performer with Musical {
  // ···
}
class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

要实现 mixin, 创建一个类继承 Object 并且声明没有构造函数。除非你想要你的 mixin当做一个标准类可以使用,要不使用 mixin 关键字来代替 class。例如:

mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}

为了明确只有确定类相关可以使用 minxin - 例如,你的minxin 可以调用未定义的方法 - 使用 on 来确定所必须的超类:

mixin MusicalPerformer on Musician {
  // ···
}

Version note: minxin 关键字是在Dart 2.1 代码中被引入的,在早期通常使用 abstract class 代替。

注:关于minxin 更详细的介绍和使用,可以参照这篇文章

Class variables and methods

使用 static 关键字来实现类范围的变量和方法.

Static variables

静态变量(类变量) 对于类范围的状态和常量是有用的:

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

静态变量直到他们被使用的时候才会初始化.

Static methods

静态方法(类方法)不能操作一个实例变量,并且不能够访问 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);
}

你可以使用静态方法当做编译期常量.例如,你可以传递一个静态方法当做一个参数给一个常量构造函数。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容