Dart 語言入門 (四)

weixin_34146805發表於2018-12-24

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.

1256700-f4aa2cca5f8ef647.png

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

你可以使用靜態方法當做編譯期常量.例如,你可以傳遞一個靜態方法當做一個引數給一個常量建構函式。

相關文章