前面说到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
修饰构造函数,具体意义不多说了,网上给出的例子都是方便调用者可以使用同一个对象,并不是单纯意义上的工厂设计模式。
评论