Dart是一种面向对象的语言,同时具有类和基于mixin的继承性。每个对象都是一个类的实例,除Null以外的所有类都继承自object。基于mixin的继承,意味着尽管每个类(除了顶级类Object?)只有一个超类,但类体可以在多个类层次中重用。
对象主要包含由函数和数据(方法和实例变量)组成的成员。如果要调用类方法或者成员变量,需要在一个对象基础上调用它。
扩展方法是在不改变类或创建子类的情况下向类添加功能的一种方法。
实例变量
下面是声明实例变量的方式:
class Point {
double? x; // 定义变量 x, 初始值 null.
double? y; // 定义变量 y, 初始值 null.
double z = 0; // 定义变量 z, 初始值 0.
}
所有未初始化的实例变量的值都是null。所有实例变量都会生成一个隐式的getter方法,非 final实例变量和late final实例变量也会生成隐式setter方法。
实例变量可以是final,但是只能被赋值一次。可以通过构造函数参数赋值或者构造函数列表初始化赋值,代码如下:
class ProfileMark {
final String name;
final DateTime start = DateTime.now();
//构造函数赋值
ProfileMark(this.name);
//命名构造函数--列表初始化赋值
ProfileMark.unnamed() : name = '';
}
如果需要在构造函数体启动后赋值给final实例变量,你可以使用以下方法之一:
- 使用工厂构造函数
- 使用late final,但是要注意late final 可能为空。
构造函数
通过创建与其类同名的函数来声明构造函数,最常见的构造函数形式是生成构造函数,创建一个类的新实例,如下:
class Point {
double x = 0;
double y = 0;
Point(double x, double y) {
// 初始化实例变量
this.x = x;
this.y = y;
}
}
关键字this指的是当前实例。只有在名称冲突时才使用this,否则Dart可以会省略this关键字。
将构造函数参数赋值给实例变量的模式非常常见,因此Dart使用了初始化形式参数来简化这一过程。初始化参数还可以用于初始化不可空或final实例变量,这两者都必须初始化或提供默认值。简化代码如下:
class Point {
final double x;
final double y;
Point(this.x, this.y);
}
- 默认构造函数
如果不声明构造函数,则会为您提供默认构造函数。默认构造函数没有参数,并调用超类中的无参数构造函数
子类不从父类继承构造函数。没有声明构造函数的子类只有默认(无参数)构造函数。
- 命名构造函数
使用命名构造函数为一个类实现多个构造函数或提供额外的清晰度。
class Point {
double x;
double y;
// Named constructor
Point.origin(double x , double y){
this.x = x;
this.y = y;
}
}
//使用
Point point = new Point().origin(1.5, 2.5);
- 调用超类非默认构造函数
超类的构造函数在构造函数体的开头被调用。如果还使用了初始化列表,则在父类被调用之前执行它。执行顺序如下:
- 初始化列表
- 父类的无参数构造函数
- 当前类的无参数构造函数
代码演示:
class Person {
String? firstName;
Person.from(String name): assert( name != null) {
print('in Person');
}
}
class Employee extends Person {
// Person 对象没有默认构造方法;
// 子对象必须调用super.fromJson().
Employee.from(super.data) : super.from() {
print('in Employee');
}
}
void main() {
var employee = Employee.from("Lucy");
print(employee);
// Prints:
// in Person
// in Employee
// Instance of 'Employee'
}
以上演示了代码执行过程,执行顺序为:
- assert( name != null)
- Person.from
- Employee.from
为了避免必须手动将每个参数传递到构造函数中调用,可以使用超类初始化式参数将参数转发到指定的或默认的超类构造函数。如下:
class Vector2d {
final double x;
final double y;
Vector2d(this.x, this.y);
}
class Vector3d extends Vector2d {
final double z;
//等价于 Vector3d(final double x, final double y, this.z) : super(x, y);
Vector3d(super.x, super.y, this.z);
}
- 重定向构造函数
有时构造函数的唯一目的是重定向到同一类中的另一个构造函数。重定向构造函数的主体为空,构造函数调用(使用this而不是类名)出现在冒号(:)之后:
class Point {
double x, y;
// The main constructor for this class.
Point(this.x, this.y);
// 委托给主构造函数
Point.alongXAxis(double x) : this(x, 0);
}
- 常量构造函数
如果类生成的对象永远不会更改,则可以将这些对象设置为编译时常量。为此,需要定义一个const构造函数,并确保所有实例变量都是final变量。
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
final double x, y;
const ImmutablePoint(this.x, this.y);
}
- 工厂构造函数
如果希望不总是创建类的新实例的构造函数时,使用factory关键字。例如,工厂构造函数可能从缓存返回一个实例,也可能返回子类型的实例。
注意:工厂构造函数不能访问this
在下面的例子中,Logger工厂构造函数从缓存返回对象,而Logger. fromjson工厂构造函数从JSON对象初始化一个final变量:
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) {
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);
方法
方法是为对象提供行为的函数。
- 实例方法
对象上的实例方法可以访问实例变量。下面的例子中的distanceTo()方法是一个实例方法的例子:
import 'dart:math';
class Point {
final double x;
final double 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);
}
}
- 操作符方法
操作符是具有特殊名称的实例方法。Dart允许使用以下名称定义操作符:
< | + | | | >>> |
> | / | ^ | [] |
<= | ~/ | & | []= |
>= | * | << | ~ |
- | % | >> | == |
以下代码演示了 + 和 - 操作符:
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);
}
void main() {
final v = Vector(2, 3);
final w = Vector(2, 2);
assert(v + w == Vector(4, 5));
assert(v - w == Vector(0, 1));
}
- getter和setter方法
getter和setter是提供对对象属性的读写访问的特殊方法。每个实例变量都有一个隐式的getter,如果合适的话还会加上一个setter。可以通过使用get和set关键字实现getter和setter来创建其他属性。代码演示如下:
class Rectangle {
double left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
// 如下定义了两个计算属性: right bottom.
double get right => left + width;
set right(double value) => left = value - width;
double get bottom => top + height;
set bottom(double value) => top = value - height;
}
void main() {
var rect = Rectangle(3, 4, 20, 15);
assert(rect.left == 3);
rect.right = 12;
assert(rect.left == -8);
}
- 抽象方法
Instance、getter和setter方法可以是抽象的,定义一个接口,但将其实现留给其他类。抽象方法只能存在于抽象类中。
abstract class Doer {
void doSomething(); // 定义抽象方法
}
class EffectiveDoer extends Doer {
void doSomething() {
//Todo
}
}
抽象类
使用abstract修饰符来定义一个抽象类——不能实例化的类。抽象类在定义接口时很有用,通常还带有一些实现。如果希望抽象类看起来是可实例化的,请定义一个工厂构造函数。
抽象类通常有抽象方法。下面是一个声明具有抽象方法的抽象类的例子:
abstract class AbstractContainer {
// Define constructors, fields, methods...
void updateChildren(); // 抽象方法.
}
类继承
使用extends创建子类,使用super引用父类:
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ···
}
class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ···
}
- 方法重写
子类可以重写实例方法(包括操作符)、getter和setter。使用@override注释来表示重写一个成员:
class Television {
// ···
set contrast(int value) {...}
}
class SmartTelevision extends Television {
@override
set contrast(num value) {...}
// ···
}
重写的方法声明必须在以下几个方面与它覆盖的方法(或方法)匹配:
- 返回类型必须与被重写方法的返回类型(或其子类型)相同。
- 参数类型必须与被重写方法的参数类型相同(或其超类型)。
- 如果被重写的方法接受n个位置参数,那么重写的方法也必须接受n个位置参数。
- 泛型方法不能覆盖非泛型方法,非泛型方法也不能覆盖泛型方法。
拓展方法
扩展方法是向现有库添加功能的一种方法。
下面是一个在string_apis.dart中定义的名为parseInt()的字符串上使用扩展方法的示例:
import 'string_apis.dart';
...
print('42'.padLeft(5));
print('42'.parseInt());
后面会专门使用一个章节介绍拓展方法。
枚举类型
枚举类型,通常称为枚举或枚举,是一种特殊的类,用于表示固定数量的常量值。
所有枚举自动扩展Enum类。它们也是密封的(sealed),这意味着它们不能子类化、实现、混合或以其他方式显式实例化。
enum Color { red, green, blue }
下面是一个示例,它声明了一个增强的枚举,其中包含多个实例、实例变量、getter和实现的接口:
enum Vehicle implements Comparable<Vehicle> {
car(tires: 4, passengers: 5, carbonPerKilometer: 400),
bus(tires: 6, passengers: 50, carbonPerKilometer: 800),
bicycle(tires: 2, passengers: 1, carbonPerKilometer: 0);
const Vehicle({
required this.tires,
required this.passengers,
required this.carbonPerKilometer,
});
final int tires;
final int passengers;
final int carbonPerKilometer;
int get carbonFootprint => (carbonPerKilometer / passengers).round();
@override
int compareTo(Vehicle other) => carbonFootprint - other.carbonFootprint;
}
使用枚举:
final favoriteColor = Color.blue;
if (favoriteColor == Color.blue) {
print('Your favorite color is blue!');
}
//获取枚举的index
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
//switch case
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'
}
向类中添加特性:mixins
Mixins是在多个类层次结构中重用类代码的一种方式。
要使用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;
}
}
要实现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');
}
}
}
如果想要限制可以使用mixin的类型。例如,mixin可能依赖于能够调用mixin没有定义的方法。如下面的例子所示,你可以通过使用on关键字来指定所需的超类来限制mixin的使用:
class Musician {
// ...
}
mixin MusicalPerformer on Musician {
// ...
}
class SingerDancer extends Musician with MusicalPerformer {
// ...
}
在前面的代码中,只有扩展或实现了Musician类才能使用mixin MusicalPerformer。因为SingerDancer扩展了SingerDancer,SingerDancer可以混合在MusicalPerformer中。
类变量和方法
使用static关键字实现类范围的变量和方法。
- 静态变量
class Queue {
static const initialCapacity = 16;
// ···
}
void main() {
assert(Queue.initialCapacity == 16);
}
静态变量在使用之前不会初始化。
- 静态方法
静态方法(类方法)不操作实例,因此不能访问实例。但是它们可以访问静态变量。如下面的示例所示,可以直接在类上调用静态方法。
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);
}
对于常见的或广泛使用的实用程序和功能,考虑使用顶级函数,而不是静态方法。