做者 | 弗拉德
来源 | 弗拉德(公众号:fulade_me)编程
Dart是一种面向对象的语言,全部对象都是一个类的实例,而全部的类都继承自Object
类。每一个除了Object
类以外的类都只有一个超类,一个类的代码能够在其它多个类继承中重复使用。json
下面是声明实例变量的示例:api
class Point { double x; // 声明 double 变量 x 并初始化为 null。 double y; // 声明 double 变量 y 并初始化为 null double z = 0; // 声明 double 变量 z 并初始化为 0。 }
全部未初始化的实例变量其值均为null
。缓存
全部实例变量均会隐式地声明一个Getter
方法,非final
类型的实例变量还会隐式地声明一个Setter
方法编程语言
class Point { double x; double y; } void main() { var point = Point(); point.x = 4; // 使用 x 的 Setter 方法。 assert(point.x == 4); // 使用 x 的 Getter 方法。 assert(point.y == null); // 默认值为 null。 }
若是你在声明一个实例变量的时候就将其初始化(而不是在构造函数或其它方法中),那么该实例变量的值就会在对象实例建立的时候被设置,该过程会在构造函数以及它的初始化器列表执行前。ide
对象的成员由函数和数据(即方法和实例变量)组成。方法的调用要经过对象来完成,这种方式能够访问对象的函数和数据。
使用.
来访问对象的实例变量或方法:函数
var p = Point(2, 2); // 获取 y 值 assert(p.y == 2); // 调用变量 p 的 distanceTo() 方法。 double distance = p.distanceTo(Point(4, 4));
使用 ?.
代替.
能够避免由于左边表达式为null
而致使的问题:post
// If p is non-null, set a variable equal to its y value. var a = p?.y;
方法是对象提供行为的函数。this
对象的实例方法能够访问实例变量和this
。下面的distanceTo()
方法就是一个实例方法的例子:code
import 'dart:math'; class Point { double x, y; Point(this.x, this.y); double distanceTo(Point other) { var dx = x - other.x; var dy = y - other.y; return sqrt(dx * dx + dy * dy); } }
子类能够重写父类的实例方法(包括操做符)、 Getter
以及 Setter
方法。你可使用 @override
注解来表示你重写了一个成员:
class SmartTelevision extends Television { @override void turnOn() {...} // ··· }
若是调用了对象中不存在的方法或实例变量将会触发noSuchMethod
方法,你能够重写noSuchMethod
方法来追踪和记录这一行为:
class A { // 除非你重写 noSuchMethod,不然调用一个不存在的成员会致使 NoSuchMethodError。 @override void noSuchMethod(Invocation invocation) { print('你尝试使用一个不存在的成员:' + '${invocation.memberName}'); } }
你不能调用一个未实现的方法除非知足下面其中的一个条件:
dynamic
类型。noSuchMethod
方法且具体的实现与Object
中的不一样。使用关键字static
能够声明类变量或类方法。
静态变量(即类变量)经常使用于声明类范围内所属的状态变量和常量:
class Queue { static const initialCapacity = 16; // ··· } void main() { assert(Queue.initialCapacity == 16); }
静态变量在其首次被使用的时候才被初始化。
静态方法(即类方法)不能被一个类的实例访问,一样地,静态方法内也不可使用关键字this
:
import 'dart:math'; class Point { double x, y; Point(this.x, this.y); static double 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); }
可使用构造函数来建立一个对象。构造函数的命名方式能够为类名或类名.
标识符的形式。例以下述代码分别使用Point()
和Point.fromJson()
两种构造器来建立Point
对象:
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});
一些类提供了常量构造函数。使用常量构造函数,在构造函数名以前加 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
关键字。例以下面的例子中咱们建立了一个常量Map
:
// 这里有不少 const 关键字 const pointAndLine = const { 'point': const [const ImmutablePoint(0, 0)], 'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)], };
根据上下文,你能够只保留第一个const
关键字,其他的所有省略:
// 只须要一个 const 关键字,其它的则会隐式地根据上下文进行关联。 const pointAndLine = { 'point': [ImmutablePoint(0, 0)], 'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)], };
可是若是没法根据上下文判断是否能够省略const
,则不能省略掉 const
关键字,不然将会建立一个非常量对象 例如:
var a = const ImmutablePoint(1, 1); // 建立一个常量 (Creates a constant) var b = ImmutablePoint(1, 1); // 不会建立一个常量 assert(!identical(a, b)); // 这两变量并不相同
可使用Object
对象的runtimeType
属性在运行时获取一个对象的类型,该对象类型是Type
的实例。
var = Point(2, 2); print('The type of a is ${a.runtimeType}');
声明一个与类名同样的函数便可声明一个构造函数(对于命名式构造函数 还能够添加额外的标识符)。大部分的构造函数形式是生成式构造函数,其用于建立一个类的实例:
class Point { double x, y; Point(double x, double y) { // 还会有更好的方式来实现此逻辑,敬请期待。 this.x = x; this.y = y; } }
使用 this
关键字引用当前实例。
对于大多数编程语言来讲在构造函数中为实例变量赋值的过程都是相似的,而Dart
则提供了一种特殊的语法糖来简化该步骤:
class Point { double x, y; // 在构造函数体执行前用于设置 x 和 y 的语法糖。 Point(this.x, this.y); }
若是你没有声明构造函数,那么Dart
会自动生成一个无参数的构造函数而且该构造函数会调用其父类的无参数构造方法。
子类不会继承父类的构造函数,若是子类没有声明构造函数,那么只会有一个默认无参数的构造函数。
能够为一个类声明多个命名式构造函数来表达更明确的意图:
class Point { double x, y; Point(this.x, this.y); // 命名式构造函数 Point.origin() { x = 0; y = 0; } }
构造函数是不能被继承的,这将意味着子类不能继承父类的命名式构造函数,若是你想在子类中提供一个与父类命名构造函数名字同样的命名构造函数,则须要在子类中显式地声明。
默认状况下,子类的构造函数会调用父类的匿名无参数构造方法,而且该调用会在子类构造函数的函数体代码执行前,若是子类构造函数还有一个初始化列表,那么该初始化列表会在调用父类的该构造函数以前被执行,总的来讲,这三者的调用顺序以下:
若是父类没有匿名无参数构造函数,那么子类必须调用父类的其中一个构造函数,为子类的构造函数指定一个父类的构造函数只需在构造函数体前使用:
指定。
由于参数会在子类构造函数被执行前传递给父类的构造函数,所以该参数也能够是一个表达式,好比一个函数:
class Employee extends Person { Employee() : super.fromJson(defaultData); // ··· }
除了调用父类构造函数以外,还能够在构造函数体执行以前初始化实例变量。每一个实例变量之间使用逗号分隔。
// 使用初始化列表在构造函数体执行前设置实例变量。 Point.fromJson(Map<String, double> json) : x = json['x'], y = json['y'] { print('In Point.fromJson(): ($x, $y)'); }
在开发模式下,你能够在初始化列表中使用assert
来验证输入数据:
Point.withAssert(this.x, this.y) : assert(x >= 0) { print('In Point.withAssert(): ($x, $y)'); }
有时候类中的构造函数会调用类中其它的构造函数,该重定向构造函数没有函数体,只需在函数签名后使用:
指定须要重定向到的其它构造函数便可:
class Point { double x, y; // 该类的主构造函数。 Point(this.x, this.y); // 委托实现给主构造函数。 Point.alongXAxis(double x) : this(x, 0); }
若是类生成的对象都是不会变的,那么能够在生成这些对象时就将其变为编译时常量。你能够在类的构造函数前加上const
关键字并确保全部实例变量均为final
来实现该功能。
class ImmutablePoint { static final ImmutablePoint origin = const ImmutablePoint(0, 0); final double x, y; const ImmutablePoint(this.x, this.y); }
常量构造函数建立的实例并不老是常量。
使用factory
关键字标识类的构造函数将会令该构造函数变为工厂构造函数,这将意味着使用该构造函数构造类的实例时并不是老是会返回新的实例对象。例如,工厂构造函数可能会从缓存中返回一个实例,或者返回一个子类型的实例。
在以下的示例中,Logger
的工厂构造函数从缓存中返回对象,和 Logger.fromJson
工厂构造函数从JSON
对象中初始化一个最终变量。
class Logger { final String name; bool mute = false; // _cache 变量是库私有的,由于在其名字前面有下划线。 static final Map<String, Logger> _cache = <String, Logger>{}; factory Logger(String name) { return _cache.putIfAbsent( name, () => Logger._internal(name)); } factory Logger.fromJson(Map<String, Object> json) { return Logger(json['name'].toString()); } Logger._internal(this.name); void log(String msg) { if (!mute) print(msg); } }
工厂构造函的调用方式与其余构造函数同样:
var logger = Logger('UI'); logger.log('Button clicked'); var logMap = {'name': 'UI'}; var loggerJson = Logger.fromJson(logMap);
使用关键字abstract
标识类可让该类成为抽象类,抽象类将没法被实例化。抽象类经常使用于声明接口方法、有时也会有具体的方法实现。若是想让抽象类同时可被实例化,能够为其定义工厂构造函数。
抽象类经常会包含抽象方法。下面是一个声明具备抽象方法的抽象类示例:
// 该类被声明为抽象的,所以它不能被实例化。 abstract class AbstractContainer { // 定义构造函数、字段、方法等…… void updateChildren(); // 抽象方法。 }
每个类都隐式地定义了一个接口并实现了该接口,这个接口包含全部这个类的实例成员以及这个类所实现的其它接口。若是想要建立一个A
类支持调用B
类的API且不想继承B
类,则能够实现B
类的接口。
一个类能够经过关键字implements
来实现一个或多个接口并实现每一个接口定义的 API:
// Person 类的隐式接口中包含 greet() 方法。 class Person { // _name 变量一样包含在接口中,但它只是库内可见的。 final _name; // 构造函数不在接口中。 Person(this._name); // greet() 方法在接口中。 String greet(String who) => '你好,$who。我是$_name。'; } // Person 接口的一个实现。 class Impostor implements Person { get _name => ''; String greet(String who) => '你好$who。你知道我是谁吗?'; } String greetBob(Person person) => person.greet('小芳'); void main() { print(greetBob(Person('小芸'))); print(greetBob(Impostor())); }
若是须要实现多个类接口,可使用逗号分割每一个接口类:
class Point implements Comparable, Location { }
使用extends
关键字来建立一个子类,并可以使用super
关键字引用一个父类:
class Television { void turnOn() { _illuminateDisplay(); _activateIrSensor(); } } class SmartTelevision extends Television { void turnOn() { super.turnOn(); _bootNetworkInterface(); _initializeMemory(); _upgradeApps(); } }
Mixin
是一种在多重继承中复用某个类中代码的方法模式。
使用with
关键字并在其后跟上Mixin
类的名字来使用Mixin
模式:
class Musician extends Performer with Musical { // ··· } class Maestro extends Person with Musical, Aggressive, Demented { Maestro(String maestroName) { name = maestroName; canConduct = true; } }
定义一个类继承自Object
而且不为该类定义构造函数,这个类就是 Mixin
类,除非你想让该类与普通的类同样能够被正常地使用,不然可使用关键字mixin
替代class
让其成为一个单纯的Mixin
类:
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'); } } }
可使用关键字on
来指定哪些类可使用该Mixin
类,好比有 Mixin
类 A
,可是A
只能被B
类使用,则能够这样定义 `A:
class Musician { // ... } mixin MusicalPerformer on Musician { // ... } class SingerDancer extends Musician with MusicalPerformer { // ... }
Dart 2.7 中引入的Extension
方法是向现有库添加功能的一种方式。
这里是一个在 String 中使用 extension
方法的样例,咱们取名为 parseInt()
,它在 string_apis.dart
中定义:
extension NumberParsing on String { int parseInt() { return int.parse(this); } double parseDouble() { return double.parse(this); } }
而后使用string_apis.dart
里面的parseInt()
方法
import 'string_apis.dart'; print('42'.padLeft(5)); // Use a String method. print('42'.parseInt()); // Use an extension method.