### 类(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) {
// ...
}
}
~~~