学习笔记,旨在于快速入门和学习Dart,其中可能会有理解错误,请指出,一起学习。
系列文章
2.1、Dart语言基础:变量、运算符
2.2、Dart语言基础:函数与闭包
2.3、Dart语言基础:面向对象
2.4、Dart语言基础:异步
2.5、Dart语言基础:库与包
...
零、概述
Dart语言在面向对象编程提供了类Class
、继承Inheritance
、混合Mixins
、接口和抽象类Interfaces and abstract classes
,还有泛化generics
等。
泛化在业务开发层面用到的比较少,本篇章介绍前面几个概念,后续专门一章节介绍泛化。
包括以下内容:
1、类的定义 和 继承,成员变量、成员方法,构造器。
2、支持类的静态化,即支持类方法和类变量。
3、支持抽象类Abstract Class。
4、支持实现Implements、扩展Extension、混淆mixins。
一、类 与 继承
- Dart中万物皆为对象,所有的类都继承于 Object基类(null除除外)。
- 类声明关键字
class
,继承关键字extends
。 - 关键字
this
,指向当前对象。
Dart规范是忽略this的,只有在存在命名冲突的情况下才需要用this。 - 关键字
super
,指向父类对象,显式的调用父类的方法。
1、类的声明 和 成员变量的访问
class Pet {
String? name;
int weight = 0;
void saySomething() {
print('name is $name');
}
}
// 0、默认的无参数构造器
var pet = Pet();
pet.name = "gigi";
// 1、访问实例属性:gigi
print(pet.name);
// 2、调用实例方法:name is gigi; weight = 0
pet.saySomething();
// 3、对象的类型是:Pet
print('对象的类型是:${pet.runtimeType}');
- 语法格式:关键字
class
。 - 使用 操作符
.
访问 成员变量 和 成员方法。 -
.runtimeType
获取对象类型。
var p2;
// 1、null,p2为空则返回null
print(p2?.name);
- 为了避免左操作符为空导致的异常,可以使用操作符
?.
。
2、成员变量
class Point {
double? x; // Declare instance variable x, initially null.
double? y; // Declare y, initially null.
double z = 0; // Declare z, initially 0.
}
- 未初始化的成员变量,默认为null。
- 非延迟的成员变量 在类对象被创建的时候,已经赋值。
2.1、final 成员变量
- 不管任何场景下,只会被初始化一次。
- 使用构造器或者初始化列表初始化
final成员变量
。
class ProfileMark {
final String name;
final DateTime start = DateTime.now();
ProfileMark(this.name);
ProfileMark.unnamed() : name = '';
}
3、构造函数和析构函数
- 由于Dart支持自动垃圾回收机制,因此无需手动写析构函数。
3.1、未命名构造器 和 命名构造器
class Point {
double? x;
double? y;
}
// 1、null,null
var p4 = new Point();
print("${p4.x}, ${p4.y}");
- 如果没有提供构造器,则会提供没有参数的默认未命名构造器。
class Point {
double? x;
double? y;
// 1、带参数的未命名构造器
Point(double x, double y) {
this.x = x;
this.y = y;
}
// 语法糖,等价于上面语法
Point(this.x, this.y);
// 2、命名构造器ClassName.identifier
Point.fromJson({x=0, y=0}) {
this.x = x;
this.y = y;
}
}
void class_demo_2() {
var p1 = new Point(6, 8);
print("${p1.x}, ${p1.y}");
var p2 = new Point.fromJson(x:10.0, y: 10.0);
print("${p2.x}, ${p2.y}");
}
- 构造函数的名称 可以是
类名本身
,或者 命名构造器类名.方法名
Constructor names can be either ClassName or ClassName.identifier
class Point {
double? x;
double? y;
// 声明带参数的未命名构造器
Point(this.x, this.y);
// Error: 'Point' is already declared in this scope.
Point();
}
class Rect extends Point {
}
// 2、构造器无法被继承。Error: Too many positional arguments: 0 allowed, but 2 found.
var p3 = Rect(6, 8);
- 未命名构造器,即
ClassName
,有且只有一个,不管参数多少。
否则编译报错Error: 'Point' is already declared in this scope.
- 父类构造器无法被子类继承。
如上Point(this.x, this.y);
Rect无法使用。 - 如果父类定义了
带参数的未命名构造器
,则子类无法使用默认构造器。
否则编译报错Error: The superclass, 'Point', has no unnamed constructor that takes no arguments.
3.2、继承链中的构造器
继承链中,构造器的调用顺序
1、初始化列表,initializer list
2、父类的未命名无参数构造器,superclass’s no-arg constructor
3、子类的未命名无参数构造器,main class’s no-arg constructor子类 需要手动调用 父类构造器。
class Point {
double? x;
double? y;
Point(this.x, this.y);
Point.fromMap(Map data) : x=data['x'], y=data['y'] {
}
}
class Rect extends Point {
Rect(double x, double y) : super(x, y);
Rect.fromMap(Map data) : super.fromMap(data) {
}
}
void class_demo_2() {
var p3 = Rect(10, 10);
print("${p3.x}, ${p3.y}");
var p5 = Rect.fromMap({'x':20.0, 'y':20.0});
print("${p5.x}, ${p5.y}");
}
- 初始化列表作用:调用其他构造器、简单语句执行等
class Point {
final double x;
final double y;
final double distanceFromOrigin;
Point(this.x, this.y);
// 1、简单的计算语句
Point(double x, double y)
: x = x,
y = y,
distanceFromOrigin = sqrt(x * x + y * y);
// 2、简单的判断语句
Point.withAssert(this.x, this.y) : assert(x >= 0) {
}
// 3、简单的赋值语句
Point.fromJson(Map json) : x = json['x']!, y = json['y']! {
}
// 4、重定向构造器
Point.alongXAxis(double x) : this(x, 0);
}
3.3、常量构造器,constant constructors
- 常量构造器在编译期已决议,并且所有的变量必须为
final
类型。
class ImmutablePoint {
final double x, y;
static const ImmutablePoint origin = ImmutablePoint(0, 0);
const ImmutablePoint(this.x, this.y);
}
// 实例化
var p = const ImmutablePoint(2, 2);
- 构建两个完全相同的常量类,只会创建一个常量类,多次调用值是相同的。
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
// They are the same instance!
assert(identical(a, b));
常量构造器的初始化
常量构造器并不总是创建常量对象。 Constant constructors don’t always create constants.
- 常量构造器 调用前 必须使用const,才能得到常量对象。
If a constant constructor is outside of a constant context and is invoked without const, it creates a non-constant object:
var a = const ImmutablePoint(1, 1); // Creates a constant
var b = ImmutablePoint(1, 1); // Does NOT create a constant
assert(!identical(a, b)); // NOT the same instance!
- 在 "常量上下文" 中,在构造器或字面量前面可以忽略
const
关键字。
Within a constant context, you can omit the const before a constructor or literal.
// Lots of const keywords here.
const pointAndLine = const {
'point': const [const ImmutablePoint(0, 0)],
'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};
// 等价于
// Only one const, which establishes the constant context.
const pointAndLine = {
'point': [ImmutablePoint(0, 0)],
'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};
3.4、工厂构造器(Factory constructors),关键字factory
主要用途:
1、创建 不返回新实例对象的构造器。例如,内存缓存中的实例对象、返回对象的子类型。
Use the factory keyword when implementing a constructor that doesn’t always create a new instance of its class.
2、初始化类的
final
属性变量 。
class Logger {
final String name;
bool mute = false;
// 内部存储
static final Map _cache = {};
// 内部 命名构造器
Logger._internal(this.name);
factory Logger(String name) {
// 存在key就获取值,不存在则添加到map, 然后返回值
// 1、在构造器函数内,初始化final成员变量。
return _cache.putIfAbsent(name, () => Logger._internal(name));
}
factory Logger.fromJson(Map json) {
return Logger(json['name'].toString());
}
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);
- 在构造器函数内,初始化final成员变量。
If you need to assign the value of a final instance variable after the constructor body starts.
factory Logger(String name) {
// 存在key就获取值,不存在则添加到map, 然后返回值
return _cache.putIfAbsent(name, () => Logger._internal(name));
}
- 工厂构造器无法访问
this
关键字。
Factory constructors have no access to this.
4、成员方法
- 4.1、运算符方法,语法:
operator 运算符
运算符 是特殊名称的实例方法。Operators are instance methods with special names.
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));
}
- 4.2、Getters and setters方法,关键字
get\set
提供简单便捷的方式,支持简单计算,类似Swift的计算属性功能。
class Rectangle {
double left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
// Define two calculated properties: right and 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);
}
5、类继承
5.1、关键字extends
、super
class Television {
void turnOn() {
}
}
class SmartTelevision extends Television {
void turnOn() {
// 调用父类方法
super.turnOn();
}
}
5.2、重写方法,关键字@override
class SmartTelevision extends Television {
@override
void turnOn() {
...
}
}
- 可以重写的方法,包括实例方法、操作符方法、getter/setter方法。
- 重写的方法必须满足一下几点
1、重写方法的返回值的类型,必须和原方法一样(或是其类型的子类)。
2、重写方法的参数类型,必须和原方法一样(或是其类型的子类)。
3、重写方法的位置参数个数,必须和原方法的位置参数个数一样。
4、泛型方法和非泛型的方法无法相互转化,必须同为泛型方法或者都不是。
- 捕获调用类中不存在的方法或者变量,可以重写
void noSuchMethod(Invocation invocation)
方法。
class HelloClass {
@override
void noSuchMethod(Invocation invocation) {
print('You tried to use a non-existent member: ${invocation.memberName}');
}
}
二、类方法和类变量(Class variables and methods)
- 声明类方法和类变量,必须在前面使用关键字
static
。
1、类变量(静态变量)
// 类变量
class Queue {
static const initialCapacity = 16;
}
void main() {
assert(Queue.initialCapacity == 16);
}
- 类变量:适用于 类范围(class-wide) 内的状态和常量。
- 类变量 是在第一次被使用的时候 才进行初始化。
2、类方法(静态方法)
- 静态方法无法访问
this
指针,不能访问实例变量;可以访问类变量(static variables)
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);
}
三、抽象类
1、抽象类 和 抽象方法,关键字abstract
- 抽象方法不能被实例化,主要用来抽象通用的逻辑。
abstract class Doer {
void doSomething();
}
class EffectiveDoer extends Doer {
void doSomething() {
}
}
抽象方法:实例方法、getter/setter方法,都可以声明为抽象方法。
If you want your abstract class to appear to be instantiable, define a factory constructor.
四、实现,implements
1、类的隐式接口
- 任何的类 都隐式定义 一个接口,这个接口包含了 类全部的实例成员(变量和方法,但不包括构造函数) 及 类实现的所有接口。
Every class implicitly defines an interface containing all the instance members of the class and of any interfaces it implements.
// A person. The implicit interface contains greet().
class Person {
// In the interface, but visible only in this library.
final String _name;
// Not in the interface, since this is a constructor.
Person(this._name);
// In the interface.
String greet(String who) => 'Hello, $who. I am $_name.';
}
// An implementation of the Person interface.
class Impostor implements Person {
String get _name => '';
String greet(String who) => 'Hi $who. Do you know who I am?';
}
String greetBob(Person person) => person.greet('Bob');
void main() {
print(greetBob(Person('Kathy')));
print(greetBob(Impostor()));
}
2、类 支持多个 接口实现。
class Point implements Comparable, Location {...}
五、扩展,extension
主要用来为 现有库(library)和APIs 动态增加新的功能。
1、扩展的定义
extension on {
()*
}
- 扩展可以有名字,如下
NumberParsing
。
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
double parseDouble() {
return double.parse(this);
}
}
- 可以扩展的方法,包括操作符方法、getter/setter方法、成员方法、静态方法。
- 支持 私有(本地)扩展,下划线
_
,该扩展只在当前文件或者库内部可见。
2、支持泛化扩展
例如:下面是 List
的扩展 MyFancyList
。
extension MyFancyList on List {
int get doubleLength => length * 2;
List operator -() => reversed.toList();
List> split(int at) => [sublist(0, at), sublist(at)];
}
3、命名冲突,API conflicts
- 方法1:导入import的时候,显示声明显示/隐藏(show/hide) 某个方法。
// Defines the String extension method parseInt().
import 'string_apis.dart';
// Also defines parseInt(), but hiding NumberParsing2
// hides that extension method.
import 'string_apis_2.dart' hide NumberParsing2;
// Uses the parseInt() defined in 'string_apis.dart'.
print('42'.parseInt());
- 方法2:显示定义为不同的扩展名称。
// Both libraries define extensions on String that contain parseInt(),
// and the extensions have different names.
import 'string_apis.dart'; // Contains NumberParsing extension.
import 'string_apis_2.dart'; // Contains NumberParsing2 extension.
// ···
// print('42'.parseInt()); // Doesn't work.
print(NumberParsing('42').parseInt());
print(NumberParsing2('42').parseInt());
- 方法3:前缀调用,即导入import的时候重命名,关键字
as
。
类似于JavaScript的import-as功能。
// Both libraries define extensions named NumberParsing
// that contain the extension method parseInt(). One NumberParsing
// extension (in 'string_apis_3.dart') also defines parseNum().
import 'string_apis.dart';
import 'string_apis_3.dart' as rad;
// ···
// print('42'.parseInt()); // Doesn't work.
// Use the ParseNumbers extension from string_apis.dart.
print(NumberParsing('42').parseInt());
// Use the ParseNumbers extension from string_apis_3.dart.
print(rad.NumberParsing('42').parseInt());
// Only string_apis_3.dart has parseNum().
print('42'.parseNum());
六、混淆,mixins
- 主要用处是实现多继承,复用多个现有类的代码。
1、语法格式:
- 关键字
mixin
,一般来说,混淆是一个无构造函数代码集合。
mixin Musical {
bool canPlayPiano = false;
bool canCompose = false;
bool canConduct = false;
void entertainMe() {
...
}
}
- 关键字 'on' 用来 指定
混淆
只有被特定的类或其子类
使用。
Sometimes you might want to restrict the types that can use a mixin.
class Musician {
}
// 只能被Musician及其子类使用
mixin MusicalPerformer on Musician {
}
class SingerDancer extends Musician with MusicalPerformer {
}
2、混淆的使用
关键字with
用来声明类 遵循 一个或者多个混淆。
class Musician extends Performer with Musical {
}
class Maestro extends Person with Musical, Aggressive, Demented {
Maestro(String maestroName) {
}
}
七、枚举
1、枚举的约束
- 枚举不支持继承subclass、不支持mix、不支持implement。
- 枚举无法实例化。
2、语法格式
- 关键字
enum
。
enum Color { red, green, blue }
// .index 枚举值
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
// .values 获取所有的枚举值
List colors = Color.values;
assert(colors[2] == Color.blue);
// 结合switch
var aColor = Color.blue;
switch (aColor) {
case Color.red:
print('Red as roses!');
break;
case Color.green:
print('Green as grass!');
break;
default:
print(aColor); // 'Color.blue'
}
- 每一个枚举值都包含
index
的getter方法。 - 获取所有的枚举值列表,
.values
。 - 配合
switch
可以实现多分支流程控制。