類(上)

2021-04-16 17:45 更新

Dart 是一種基于類和 mixin 繼承機制的面向對象的語言。 每個對象都是一個類的實例,所有的類都繼承于 Object. 。 基于 * Mixin 繼承* 意味著每個類(除 Object 外) 都只有一個超類, 一個類中的代碼可以在其他多個繼承類中重復使用。


使用類的成員變量

對象是由函數(shù)和數(shù)據(jù)(即方法和實例變量)組成。 方法的調用要通過對象來完成: 調用的方法可以訪問其對象的其他函數(shù)和數(shù)據(jù)。

使用 (.) 來引用實例對象的變量和方法:

var p = Point(2, 2);

// 為實例的變量 y 設置值。
p.y = 3;

// 獲取變量 y 的值。
assert(p.y == 3);

// 調用 p 的 distanceTo() 方法。
num distance = p.distanceTo(Point(4, 4));

使用 ?. 來代替 . , 可以避免因為左邊對象可能為 null , 導致的異常:

// 如果 p 為 non-null,設置它變量 y 的值為 4。
p?.y = 4;


使用構造函數(shù)

通過 構造函數(shù) 創(chuàng)建對象。 構造函數(shù)的名字可以是 ClassName 或者 ClassName.identifier。例如, 以下代碼使用 Point 和 Point.fromJson() 構造函數(shù)創(chuàng)建 Point 對象:

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

以下代碼具有相同的效果, 但是構造函數(shù)前面的的 new 關鍵字是可選的:

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

版本提示: 在 Dart 2 中 new 關鍵字變成了可選的。

一些類提供了常量構造函數(shù)。 使用常量構造函數(shù),在構造函數(shù)名之前加 const 關鍵字,來創(chuàng)建編譯時常量時:

var p = const ImmutablePoint(2, 2);

構造兩個相同的編譯時常量會產生一個唯一的, 標準的實例:

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

assert(identical(a, b)); // 它們是同一個實例。

在 常量上下文 中, 構造函數(shù)或者字面量前的 const 可以省略。 例如,下面代碼創(chuàng)建了一個 const 類型的 map 對象:

// 這里有很多的 const 關鍵字。
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};

保留第一個 const 關鍵字,其余的全部省略:

// 僅有一個 const ,由該 const 建立常量上下文。
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

如果常量構造函數(shù)在常量上下文之外, 且省略了 const 關鍵字, 此時創(chuàng)建的對象是非常量對象:

var a = const ImmutablePoint(1, 1); // 創(chuàng)建一個常量對象
var b = ImmutablePoint(1, 1); // 創(chuàng)建一個非常量對象

assert(!identical(a, b)); // 兩者不是同一個實例!

版本提示: 在 Dart 2 中,一個常量上下文中的 const 關鍵字可以被省略。


獲取對象的類型

使用對象的 runtimeType 屬性, 可以在運行時獲取對象的類型, runtimeType 屬性回返回一個 Type 對象。

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

到目前為止,我們已經(jīng)解了如何_使用_類。 本節(jié)的其余部分將介紹如何_實現(xiàn)_一個類。


實例變量

下面是聲明實例變量的示例:

class Point {
  num x; // 聲明示例變量 x,初始值為 null 。
  num y; // 聲明示例變量 y,初始值為 null 。
  num z = 0; // 聲明示例變量 z,初始值為 0 。
}

未初始化實例變量的默認人值為 “null” 。

所有實例變量都生成隱式 getter 方法。 非 final 的實例變量同樣會生成隱式 setter 方法。 有關更多信息,參考 Getters 和 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.
}

如果在聲明時進行了示例變量的初始化, 那么初始化值會在示例創(chuàng)建時賦值給變量, 該賦值過程在構造函數(shù)及其初始化列表執(zhí)行之前。


構造函數(shù)

通過創(chuàng)建一個與其類同名的函數(shù)來聲明構造函數(shù) (另外,還可以附加一個額外的可選標識符,如 命名構造函數(shù) 中所述)。 下面通過最常見的構造函數(shù)形式, 即生成構造函數(shù), 創(chuàng)建一個類的實例:

class Point {
  num x, y;

  Point(num x, num y) {
    // 還有更好的方式來實現(xiàn)下面代碼,敬請關注。
    this.x = x;
    this.y = y;
  }
}

使用 this 關鍵字引用當前實例。

提示: 近當存在命名沖突時,使用 this 關鍵字。 否則,按照 Dart 風格應該省略 this 。

通常模式下,會將構造函數(shù)傳入的參數(shù)的值賦值給對應的實例變量, Dart 自身的語法糖精簡了這些代碼:

class Point {
  num x, y;

  // 在構造函數(shù)體執(zhí)行前,
  // 語法糖已經(jīng)設置了變量 x 和 y。
  Point(this.x, this.y);
}

默認構造函數(shù)

在沒有聲明構造函數(shù)的情況下, Dart 會提供一個默認的構造函數(shù)。 默認構造函數(shù)沒有參數(shù)并會調用父類的無參構造函數(shù)。

構造函數(shù)不被繼承

子類不會繼承父類的構造函數(shù)。 子類不聲明構造函數(shù),那么它就只有默認構造函數(shù) (匿名,沒有參數(shù)) 。

命名構造函數(shù)

使用命名構造函數(shù)可為一個類實現(xiàn)多個構造函數(shù), 也可以使用命名構造函數(shù)來更清晰的表明函數(shù)意圖:

class Point {
  num x, y;

  Point(this.x, this.y);

  // 命名構造函數(shù)
  Point.origin() {
    x = 0;
    y = 0;
  }
}

切記,構造函數(shù)不能夠被繼承, 這意味著父類的命名構造函數(shù)不會被子類繼承。 如果希望使用父類中定義的命名構造函數(shù)創(chuàng)建子類, 就必須在子類中實現(xiàn)該構造函數(shù)。

調用父類非默認構造函數(shù)

默認情況下,子類的構造函數(shù)會自動調用父類的默認構造函數(shù)(匿名,無參數(shù))。 父類的構造函數(shù)在子類構造函數(shù)體開始執(zhí)行的位置被調用。 如果提供了一個 initializer list (初始化參數(shù)列表), 則初始化參數(shù)列表在父類構造函數(shù)執(zhí)行之前執(zhí)行。 總之,執(zhí)行順序如下:

  1. initializer list (初始化參數(shù)列表)
  2. superclass’s no-arg constructor (父類的無名構造函數(shù))
  3. main class’s no-arg constructor (主類的無名構造函數(shù))

如果父類中沒有匿名無參的構造函數(shù), 則需要手工調用父類的其他構造函數(shù)。 在當前構造函數(shù)冒號 (:) 之后,函數(shù)體之前,聲明調用父類構造函數(shù)。

下面的示例中,Employee 類的構造函數(shù)調用了父類 Person 的命名構造函數(shù)。

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
  if (emp is Person) {
    // Type check
    emp.firstName = 'Bob';
  }
  (emp as Person).firstName = 'Bob';
}

由于父類的構造函數(shù)參數(shù)在構造函數(shù)執(zhí)行之前執(zhí)行, 所以參數(shù)可以是一個表達式或者一個方法調用:

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

警告: 調用父類構造函數(shù)的參數(shù)無法訪問 this 。 例如,參數(shù)可以為靜態(tài)函數(shù)但是不能是實例函數(shù)。

初始化列表

除了調用超類構造函數(shù)之外, 還可以在構造函數(shù)體執(zhí)行之前初始化實例變量。 各參數(shù)的初始化用逗號分隔。

// 在構造函數(shù)體執(zhí)行之前,
// 通過初始列表設置實例變量。
Point.fromJson(Map<String, num> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x, $y)');
}

警告: 初始化程序的右側無法訪問 this 。

在開發(fā)期間, 可以使用 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(2, 3);
  print(p.distanceFromOrigin);
}


重定向構造函數(shù)

有時構造函數(shù)的唯一目的是重定向到同一個類中的另一個構造函數(shù)。 重定向構造函數(shù)的函數(shù)體為空, 構造函數(shù)的調用在冒號 (:) 之后。

class Point {
  num x, y;

  // 類的主構造函數(shù)。
  Point(this.x, this.y);

  // 指向主構造函數(shù)
  Point.alongXAxis(num x) : this(x, 0);
}

常量構造函數(shù)

如果該類生成的對象是固定不變的, 那么就可以把這些對象定義為編譯時常量。 為此,需要定義一個 const 構造函數(shù), 并且聲明所有實例變量為 final。

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

  final num x, y;

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

常量構造函數(shù)創(chuàng)建的實例并不總是常量。 更多內容,查看 使用構造函數(shù) 章節(jié)。

工廠構造函數(shù)

當執(zhí)行構造函數(shù)并不總是創(chuàng)建這個類的一個新實例時,則使用 factory 關鍵字。 例如,一個工廠構造函數(shù)可能會返回一個 cache 中的實例, 或者可能返回一個子類的實例。

以下示例演示了從緩存中返回對象的工廠構造函數(shù):

class Logger {
  final String name;
  bool mute = false;

  // 從命名的 _ 可以知,
  // _cache 是私有屬性。
  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);
  }
}

提示: 工廠構造函數(shù)無法訪問 this。

工廠構造函的調用方式與其他構造函數(shù)一樣:

var logger = Logger('UI');
logger.log('Button clicked');
以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號