前面说到Dart中一切皆为对象。而所有对象都是类的实例,并且所有类都为Object的子类。
和其他语言一样,类也是用class关键字作为声明。
1 | class TestClass{ |
构造函数
Dart具有构造函数,跟C++不一样,没有析构函数。
1 | class TestClass{ |
上述代码是标准写法。如果没有显示声明构造函数,类默认有一个无参数的构造函数。另外,Dart针对构造函数中没有内容的情况时提供了一种简写,比如上述代码可以简写如下:
1 | class TestClass{ |
没有函数重载!
Dart中没有函数重载功能,当在同一个类中定义了一个名为func的函数后,后面不能再定义一个名字相同、但返回类型和参数类型不同的函数。
同样的,因为没有函数重载,构造函数也不存在重载,即当一个类中定义了一个构造函数后,就无法再定义一个相同名字、但构造不同的函数。
1 | class TestClass{ |
如上述代码,IDE会提示错误,不允许编译。
命名构造函数
那么,如果一个类确实需要多个构造函数时该怎么做呢?Dart提供了命名构造函数,即构造函数可以定义不同的名字,通过.来实现,如:
1 | class TestClass{ |
通过命名构造函数,可以实现如其他语言中类的构造函数重载的功能。
生成对象
生成一个类的实例对象,通过new关键字实现,如下:
1 | var obj1 = new TestClass(); //默认的构造函数 |
构造函数传参
Dart中的类和Java一样,通过this关键字指向实例自身,因此标准的构造函数传参和Java一致:
1 | class TestClass{ |
同时,Dart在传参上也提供了简写,也是通过this关键字实现的:
1 | class TestClass{ |
如上述代码,这样调用构造函数将自动把值传递给类的变量。如果构造函数只用于传递值,而不需要在里面做其他逻辑,则可以这样简写:
1 | class TestClass{ |
值得一提的是,在构造函数中也可以使用可选参数来定义参数。
生成不可变的对象
Dart中可以用const修饰构造函数,这样通过该构造函数构建出来的对象是不可变的对象,此类对象可以赋值给final或const修饰的常量。
1 | class TestClass{ |
如上,除了注释掉的那一行会报错的以外,(1)、(2)、(3)、(4)行都可以编译运行。其中,被const所修饰的常量,赋值时必须用const来生成对象,不能用new;反过来,final所修饰的对象,用const或者new来生成对象都可以。
需要注意的是,使用const修饰构造函数时,其对应的类中的所有成员变量都必须用final修饰,并且不允许有函数body(即大括号{}包裹的逻辑语句)。
同时,由于成员变量被final修饰,所以构造函数必须用简写的模式来给变量赋值。例如:
1 | class TestClass{ |
上述代码中,因为变量p2被修饰为final,所以必须用简写模式赋值。
Getter & Setter
在Dart中,类中的成员变量、成员函数不存在访问权限这个概念,像Java中的private、public之类的并不存在。
1 | class TestClass{ |
如上述代码中,任何人只要生成了类的实例对象,就可以通过对象来访问、修改这个类的成员变量。但这里又和其他的语言有点不同——在Dart中,之所以可以直接调用、修改实例的变量,是因为在Dart中类的所有成员变量默认有其隐式的相同名字的Getter和Setter,如果变量被final或const修饰,那么只有Getter没有Setter。
如果要显式定义某个变量的Getter或者Setter,则分别需要用get和set关键字来实现:
1 | class TestClass{ |
如上述代码,Getter和Setter的方法名可以相同。注意get修饰的Getter方法不能有()。set修饰的Setter方法后面带有(),但调用时不能用testClass.myP1(1)这种方式,而是用=来调用Setter方法。
前面说到,每个成员变量有其隐式的Getter和Setter,所以当我们自定义Getter、Setter时,命名不能用和成员变量一样的名字。
extends & implements
类的继承通过extends关键字来实现,基本实现和操作跟Java大致相同。
1 | class BaseClass{ |
如上,TestClass集成了BaseClass,因此可以选择性地改写BaseClass的方法。
而实现接口则通过implements关键字实现,与Java不同的是dart没有interface这个关键字,因为每一个类都是一个隐式接口。以上面的BaseClass为例:
1 | class TestClass2 implements BaseClass{ |
TestClass2通过implements实现了BaseClass内定义的方法func1()、func2(),虽然BaseClass在func1()、func2()里都有定义内容,但通过implements实现后,TestClass2并不会像extends一样默认执行BaseClass的方法。
抽象类
抽象类通过abstract关键字修饰类:
1 | abstract class BaseClass{ |
可以看出,抽象类在继承和实现接口上并没有什么区别,都是强制要求实现抽象类中定义的函数。
和Java不同,dart只有抽象类,没有抽象方法。
mixin(混合)
我们知道,实现接口时可以implements多个接口,每个接口用逗号隔开:
1 | class TestClass implements BaseClass1,BaseClass2{ |
而继承只能继承一个类。某些时候,可能我们希望能继承多个类,不同的方法用不同类的实现方式——这时候,就可以使用dart提供的mixin(混合)模式。
mixin通过with关键字来实现。举个例子,现在分别有两个类ClassA和ClassB,通过这两个类组合成一个新的类ClassC,则表现为:
ClassC = ClassA with ClassB
假设ClassA中所有方法的集合为\(F_{A}\),ClassB中所有方法的集合为\(F_{B}\),ClassC中所有方法的集合为\(F_{C}\),则可理解为:
$$F_{C}=F_{B} \cup (F_{A}-F_{B})$$
即ClassC中的所有方法为ClassA和ClassB的方法的集合,如果ClassA和ClassB中有重复的方法,则取ClassB的实现:
1 | class ClassA{ |
with关键字也可以通过逗号混合多个类。例如,目前分别有ClassA、ClassB、ClassC三个类,将该三个类组合成一个新类ClassD:
ClassD = ClassA with ClassB,ClassC
这就相当于按顺序调用with:
ClassD = (ClassA with ClassB) with ClassC
例子如下:
1 | class ClassA{ |
工厂构造函数
使用factory修饰构造函数,具体意义不多说了,网上给出的例子都是方便调用者可以使用同一个对象,并不是单纯意义上的工厂设计模式。
评论