### 類(Classes)
1. ***對象***
* Dart 是一種面向對象的語言,并且支持基于mixin的繼承方式。
* Dart 語言中所有的對象都是某一個類的實例,所有的類有同一個基類--Object。
* 基于mixin的繼承方式具體是指:一個類可以繼承自多個父類。
* 使用new語句來構造一個類,構造函數的名字可能是ClassName,也可以是ClassName.identifier, 例如:
~~~
var jsonData = JSON.decode('{"x":1, "y":2}');
// Create a Point using Point().
var p1 = new Point(2, 2);
// Create a Point using Point.fromJson().
var p2 = new Point.fromJson(jsonData);
~~~
* 使用.(dot)來調用實例的變量或者方法。
~~~
var p = new 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(new Point(4, 4));
~~~
* 使用`?.`來確認前操作數不為空, 常用來替代`.`, 避免左邊操作數為null引發異常。
~~~
```
// If p is non-null, set its y value to 4.
p?.y = 4;
```
~~~
* 使用const替代new來創建編譯時的常量構造函數。
~~~
```
var p = const ImmutablePoint(2, 2);
```
~~~
* 使用runtimeType方法,在運行中獲取對象的類型。該方法將返回Type 類型的變量。
~~~
```
print('The type of a is ${a.runtimeType}');
```
~~~
2. ***實例化變量(Instance variables)***
* 在類定義中,所有沒有初始化的變量都會被初始化為null。
~~~
class Point {
num x; // Declare instance variable x, initially null.
num y; // Declare y, initially null.
num z = 0; // Declare z, initially 0.
}
~~~
* 類定義中所有的變量, Dart語言都會隱式的定義 setter 方法,針對非空的變量會額外增加 getter 方法。
~~~
class Point {
num x;
num y;
}
main() {
var point = new 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.
}
~~~
3. ***構造函數(Constructors)***
* 聲明一個和類名相同的函數,來作為類的構造函數。
~~~
class Point {
num x;
num 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 x;
num y;
// Syntactic sugar for setting x and y
// before the constructor body runs.
Point(this.x, this.y);
}
~~~
4. ***構造函數不能繼承(Constructors aren’t inherited)***
* Dart 語言中,子類不會繼承父類的命名構造函數。如果不顯式提供子類的構造函數,系統就提供默認的構造函數。
5. ***命名的構造函數(Named constructors)***
* 使用命名構造函數從另一類或現有的數據中快速實現構造函數。
~~~
class Point {
num x;
num y;
Point(this.x, this.y);
// 命名構造函數Named constructor
Point.fromJson(Map json) {
x = json['x'];
y = json['y'];
}
}
~~~
* 構造函數不能被繼承,父類中的命名構造函數不能被子類繼承。如果想要子類也擁有一個父類一樣名字的構造函數,必須在子類是實現這個構造函數。
6. ***調用父類的非默認構造函數***
* 默認情況下,子類只能調用父類的無名,無參數的構造函數; 父類的無名構造函數會在子類的構造函數前調用; 如果initializer list 也同時定義了,則會先執行initializer list 中的內容,然后在執行父類的無名無參數構造函數,最后調用子類自己的無名無參數構造函數。即下面的順序:
1. initializer list(初始化列表)
2. super class’s no-arg constructor(父類無參數構造函數)
3. main class’s no-arg constructor (主類無參數構造函數)
* 如果父類不顯示提供無名無參數構造函數的構造函數,在子類中必須手打調用父類的一個構造函數。這種情況下,調用父類的構造函數的代碼放在子類構造函數名后,子類構造函數體前,中間使用`:(colon)`分割。
~~~
class Person {
String firstName;
Person.fromJson(Map data) {
print('in Person');
}
}
class Employee extends 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';
}
~~~
7. ***初始化列表***
* 除了調用父類的構造函數,也可以通過初始化列表在子類的構造函數體前(大括號前)來初始化實例的變量值,使用逗號,分隔。如下所示:
~~~
class Point {
num x;
num y;
Point(this.x, this.y);
// 初始化列表在構造函數運行前設置實例變量。
Point.fromJson(Map jsonMap)
: x = jsonMap['x'],
y = jsonMap['y'] {
print('In Point.fromJson(): ($x, $y)');
}
}
~~~
***注意:上述代碼,初始化程序無法訪問 this 關鍵字。***
8. ***靜態構造函數***
* 如果你的類產生的對象永遠不會改變,你可以讓這些對象成為編譯時常量。為此,需要定義一個 const 構造函數并確保所有的實例變量都是 final 的。
~~~
class ImmutablePoint {
final num x;
final num y;
const ImmutablePoint(this.x, this.y);
static final ImmutablePoint origin = const ImmutablePoint(0, 0);
}
~~~
9. ***重定向構造函數***
* 有時候構造函數的目的只是重定向到該類的另一個構造函數。重定向構造函數沒有函數體,使用冒號:分隔。
~~~
class Point {
num x;
num y;
// 主構造函數
Point(this.x, this.y) {
print("Point($x, $y)");
}
// 重定向構造函數,指向主構造函數,函數體為空
Point.alongXAxis(num x) : this(x, 0);
}
void main() {
var p1 = new Point(1, 2);
var p2 = new Point.alongXAxis(4);
}
~~~
10. ***常量構造函數***
* 如果類的對象不會發生變化,可以構造一個編譯時的常量構造函數。定義格式如下:
* 定義所有的實例變量是final。
* 使用const聲明構造函數。
~~~
class ImmutablePoint {
final num x;
final num y;
const ImmutablePoint(this.x, this.y);
static final ImmutablePoint origin = const ImmutablePoint(0, 0);
}
~~~
11. ***工廠構造函數***
* 當實現一個使用 factory 關鍵詞修飾的構造函數時,這個構造函數不必創建類的新實例。例如,工廠構造函數可能從緩存返回實例,或者它可能返回子類型的實例。 下面的示例演示一個工廠構造函數從緩存返回的對象:
~~~
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 = new Logger._internal(name);
_cache[name] = logger;
return logger;
}
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) {
print(msg);
}
}
}
~~~
***注意:工廠構造函數不能用 this。***
### 抽象類
* 使用 abstract 修飾符來定義一個抽象類,該類不能被實例化。抽象類在定義接口的時候非常有用,實際上抽象中也包含一些實現。如果你想讓你的抽象類被實例化,請定義一個 工廠構造函數 。
* 抽象類通常包含 抽象方法。下面是聲明一個含有抽象方法的抽象類的例子:
~~~
// 這個類是抽象類,因此不能被實例化。
abstract class AbstractContainer {
// ...定義構造函數,域,方法...
void updateChildren(); // 抽象方法。
}
~~~
* 下面的類不是抽象類,因此它可以被實例化,即使定義了一個抽象方法:
~~~
class SpecializedContainer extends AbstractContainer {
// ...定義更多構造函數,域,方法...
void updateChildren() {
// ...實現 updateChildren()...
}
// 抽象方法造成一個警告,但是不會阻止實例化。
void doSomething();
}
~~~
### 類-隱式接口
* 每個類隱式的定義了一個接口,含有類的所有實例和它實現的所有接口。如果你想創建一個支持類 B 的 API 的類 A,但又不想繼承類 B ,那么,類 A 應該實現類 B 的接口。
* 一個類實現一個或更多接口通過用 implements 子句聲明,然后提供 API 接口要求。例如:
~~~
// 一個 person ,包含 greet() 的隱式接口。
class Person {
// 在這個接口中,只有庫中可見。
final _name;
// 不在接口中,因為這是個構造函數。
Person(this._name);
// 在這個接口中。
String greet(who) => 'Hello, $who. I am $_name.';
}
// Person 接口的一個實現。
class Imposter implements Person {
// 我們不得不定義它,但不用它。
final _name = "";
String greet(who) => 'Hi $who. Do you know who I am?';
}
greetBob(Person person) => person.greet('bob');
main() {
print(greetBob(new Person('kathy')));
print(greetBob(new Imposter()));
}
~~~
* 這里是具體說明一個類實現多個接口的例子:
~~~
class Point implements Comparable, Location {
// ...
}
~~~
### 類-擴展一個類
* 使用 extends 創建一個子類,同時 supper 將指向父類:
~~~
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ...
}
class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ...
}
~~~
* 子類可以重載實例方法, getters 方法, setters 方法。下面是個關于重寫 Object 類的方法 noSuchMethod() 的例子,當代碼企圖用不存在的方法或實例變量時,這個方法會被調用。
~~~
class A {
// 如果你不重寫 noSuchMethod 方法, 就用一個不存在的成員,會導致NoSuchMethodError 錯誤。
void noSuchMethod(Invocation mirror) {
print('You tried to use a non-existent member:' +
'${mirror.memberName}');
}
}
~~~
* 你可以使用 @override 注釋來表明你重寫了一個成員。
~~~
class A {
@override
void noSuchMethod(Invocation mirror) {
// ...
}
}
~~~
* 如果你用 noSuchMethod() 實現每一個可能的 getter 方法,setter 方法和類的方法,那么你可以使用 @proxy 標注來避免警告。
~~~
@proxy
class A {
void noSuchMethod(Invocation mirror) {
// ...
}
}
~~~