The ABC of Dart Part 3 - 类

前面说到Dart中一切皆为对象。而所有对象都是类的实例,并且所有类都为Object的子类。

和其他语言一样,类也是用class关键字作为声明。

1
2
3
class TestClass{
...
}

构造函数

Dart具有构造函数,跟C++不一样,没有析构函数。

1
2
3
4
class TestClass{
TestClass(){
}
}

上述代码是标准写法。如果没有显示声明构造函数,类默认有一个无参数的构造函数。另外,Dart针对构造函数中没有内容的情况时提供了一种简写,比如上述代码可以简写如下:

1
2
3
class TestClass{
TestClass(); //简写
}

没有函数重载!

Dart中没有函数重载功能,当在同一个类中定义了一个名为func的函数后,后面不能再定义一个名字相同、但返回类型和参数类型不同的函数。

同样的,因为没有函数重载,构造函数也不存在重载,即当一个类中定义了一个构造函数后,就无法再定义一个相同名字、但构造不同的函数。

1
2
3
4
5
6
7
class TestClass{
TestClass();

TestClass(int i){
//TODO
}
}

如上述代码,IDE会提示错误,不允许编译。

命名构造函数

那么,如果一个类确实需要多个构造函数时该怎么做呢?Dart提供了命名构造函数,即构造函数可以定义不同的名字,通过.来实现,如:

1
2
3
4
5
6
7
class TestClass{
TestClass(); //默认的构造函数

TestClass.second(int i){ //自定义命名的构造函数
//TODO
}
}

通过命名构造函数,可以实现如其他语言中类的构造函数重载的功能。

生成对象

生成一个类的实例对象,通过new关键字实现,如下:

1
2
var obj1 = new TestClass(); //默认的构造函数
var obj2 = new TestClass.second(1); //自定义命名的构造函数

构造函数传参

Dart中的类和Java一样,通过this关键字指向实例自身,因此标准的构造函数传参和Java一致:

1
2
3
4
5
6
7
class TestClass{
int param;

TestClass(int param){
this.param = param;
}
}

同时,Dart在传参上也提供了简写,也是通过this关键字实现的:

1
2
3
4
5
6
7
class TestClass{
int param1, param2;

TestClass(this.param1, this.param2){
//TODO
}
}

如上述代码,这样调用构造函数将自动把值传递给类的变量。如果构造函数只用于传递值,而不需要在里面做其他逻辑,则可以这样简写:

1
2
3
4
5
class TestClass{
int param1, param2;

TestClass(this.param1, this.param2);
}

值得一提的是,在构造函数中也可以使用可选参数来定义参数。

生成不可变的对象

Dart中可以用const修饰构造函数,这样通过该构造函数构建出来的对象是不可变的对象,此类对象可以赋值给finalconst修饰的常量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class TestClass{
final int p1, p2;

TestClass(this.p1, this.p2){
//TODO
}

const TestClass.second(this.p1, this.p2);
}

//生成对象
//const TestClass obj1 = new TestClass(1,2); //报错
const TestClass obj1 = const TestClass.second(1,2); //(1)
final TestClass obj2 = const TestClass.second(1,2); //(2)
final TestClass obj3 = new TestClass(1,2); //(3)
var obj4 = new TestClass.second(1,2); //(4)

如上,除了注释掉的那一行会报错的以外,(1)、(2)、(3)、(4)行都可以编译运行。其中,被const所修饰的常量,赋值时必须用const来生成对象,不能用new;反过来,final所修饰的对象,用const或者new来生成对象都可以。

需要注意的是,使用const修饰构造函数时,其对应的类中的所有成员变量都必须用final修饰,并且不允许有函数body(即大括号{}包裹的逻辑语句)。

同时,由于成员变量被final修饰,所以构造函数必须用简写的模式来给变量赋值。例如:

1
2
3
4
5
6
7
8
class TestClass{
int p1;
final int p2;

TestClass(int a ,this.p2){
p1 = a;
}
}

上述代码中,因为变量p2被修饰为final,所以必须用简写模式赋值。

Getter & Setter

Dart中,类中的成员变量、成员函数不存在访问权限这个概念,像Java中的privatepublic之类的并不存在。

1
2
3
4
5
6
7
8
9
class TestClass{
int p1;
int p2;
}

//调用
var testClass = new TestClass();
testClass.p1 = 1;
print(testClass.p2);

如上述代码中,任何人只要生成了类的实例对象,就可以通过对象来访问、修改这个类的成员变量。但这里又和其他的语言有点不同——在Dart中,之所以可以直接调用、修改实例的变量,是因为在Dart中类的所有成员变量默认有其隐式的相同名字的Getter和Setter,如果变量被finalconst修饰,那么只有Getter没有Setter。

如果要显式定义某个变量的Getter或者Setter,则分别需要用getset关键字来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class TestClass{
int p1;
int p2;

//int get p1 => p1;//报错
//set p1(int i) => p1 = i;//报错

int get myP1 => p1;

int get myP2{
return p2;
}

set myP1(int i) => p1 = i;

set myP2(int i){
p2 = i;
}
}

//调用
var testClass = new TestClass();
testClass.myP1 = 1; //调用Setter方法myP1
testClass.myP2 = 2; //调用Setter方法myP2

print(testClass.myP1); //调用Getter方法myP1,打印1
print(testClass.myP2); //调用Getter方法myP2,打印2

如上述代码,Getter和Setter的方法名可以相同。注意get修饰的Getter方法不能有()set修饰的Setter方法后面带有(),但调用时不能用testClass.myP1(1)这种方式,而是用=来调用Setter方法。

前面说到,每个成员变量有其隐式的Getter和Setter,所以当我们自定义Getter、Setter时,命名不能用和成员变量一样的名字。

extends & implements

类的继承通过extends关键字来实现,基本实现和操作跟Java大致相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class BaseClass{
void func1(){
//TODO
}

void func2(){
//TODO
}
}

class TestClass extends BaseClass{
@override
void func1(){
super.func1();
}
}

如上,TestClass集成了BaseClass,因此可以选择性地改写BaseClass的方法。

而实现接口则通过implements关键字实现,与Java不同的是dart没有interface这个关键字,因为每一个类都是一个隐式接口。以上面的BaseClass为例:

1
2
3
4
5
6
7
8
9
class TestClass2 implements BaseClass{
@override
void func1(){
}

@override
void func2(){
}
}

TestClass2通过implements实现了BaseClass内定义的方法func1()func2(),虽然BaseClassfunc1()func2()里都有定义内容,但通过implements实现后,TestClass2并不会像extends一样默认执行BaseClass的方法。

抽象类

抽象类通过abstract关键字修饰类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
abstract class BaseClass{
void func();
}

class TestClass extends BaseClass{
@override
void func(){
}
}

class TestClass2 implements BaseClass{
@override
void func(){
}
}

可以看出,抽象类在继承和实现接口上并没有什么区别,都是强制要求实现抽象类中定义的函数。

Java不同,dart只有抽象类,没有抽象方法。

mixin(混合)

我们知道,实现接口时可以implements多个接口,每个接口用逗号隔开:

1
2
3
class TestClass implements BaseClass1,BaseClass2{
...
}

而继承只能继承一个类。某些时候,可能我们希望能继承多个类,不同的方法用不同类的实现方式——这时候,就可以使用dart提供的mixin(混合)模式。

mixin通过with关键字来实现。举个例子,现在分别有两个类ClassAClassB,通过这两个类组合成一个新的类ClassC,则表现为:

ClassC = ClassA with ClassB

假设ClassA中所有方法的集合为\(F_{A}\),ClassB中所有方法的集合为\(F_{B}\),ClassC中所有方法的集合为\(F_{C}\),则可理解为:

$$F_{C}=F_{B} \cup (F_{A}-F_{B})$$

ClassC中的所有方法为ClassAClassB的方法的集合,如果ClassAClassB中有重复的方法,则取ClassB的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class ClassA{
void func1(){
print("ClassA func1");
}

void func2(){
print("ClassA func2");
}
}

class ClassB{
void func1(){
print("ClassB func1");
}
}

class ClassC = ClassA with ClassB;

void main(){
ClassC classC = new ClassC();
classC.func1();//输出ClassB func1
classC.func2();//输出ClassA func2
}

with关键字也可以通过逗号混合多个类。例如,目前分别有ClassAClassBClassC三个类,将该三个类组合成一个新类ClassD

ClassD = ClassA with ClassB,ClassC

这就相当于按顺序调用with

ClassD = (ClassA with ClassB) with ClassC

例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class ClassA{
void func1(){
print("ClassA func1");
}

void func2(){
print("ClassA func2");
}

void func3(){
print("ClassA func3");
}
}

class ClassB{
void func1(){
print("ClassB func1");
}

void func2(){
print("ClassB func2");
}
}

class ClassC{
void func1(){
print("ClassC func1");
}
}

class ClassD = ClassA with ClassB,ClassC;

void main(){
ClassD classD = new ClassD();
classD.func1();//输出ClassC func1
classD.func2();//输出ClassB func2
classD.func3();//输出ClassA func3
}

工厂构造函数

使用factory修饰构造函数,具体意义不多说了,网上给出的例子都是方便调用者可以使用同一个对象,并不是单纯意义上的工厂设计模式。

AudioTrack播放卡顿的现象 The ABC of Dart Part 2 - 函数

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×