- 用Flutter极速构建原生应用
- 张益珲
- 4218字
- 2021-03-30 18:08:51
3.2 Dart中的类
Dart是一门基于类和继承的面向对象语言,讲到对象,我们就不得不谈类。对象是类的实例,对象也是由类构造出来的,更通俗地讲,我们可以把类理解为对象的模板,在类中定义了对象的属性和方法。
3.2.1 自定义类与构造方法
我们之前在Dart中使用的任何数据类型其实都是类,包括描述整数的int类、描述浮点数的double类、描述字符串的string类等。在开发中,我们可以根据实际需要定义自己的类,在Dart中,使用class关键字进行类的定义,示例如下:
上面我们简单定义了一个圆形类,其中定义了3个属性,分别描述圆的半径和圆心的X、Y坐标。尽管我们没有给这个圆形类定义任何方法,但它已经是一个完整的自定义类了,我们可以通过它构造圆形对象来存储数据。
通过类创建对象需要调用类的构造方法,类的构造方法通常与类名一致,也可以定义独立名称的构造方法,但是依然需要通过类名来调用。当我们定义完一个类时,Dart默认会生成一个没有参数的构造方法,我们可以直接通过类名进行调用,例如:
类实例中封装的属性可以通过点语法来进行访问,可以进行属性值的设置,也可以进行属性值的读取。在默认情况下,如果没有对实例的某个属性进行过赋值,此属性的值就为null。但是,在进行类的定义时,也可以为属性提供默认值,例如:
构造方法是类的实例对象生成过程中的重中之重。Dart默认提供的构造方法虽然可以完整地构造出对象,但是其中定义的属性都为null值或默认值。我们也可以通过实现构造方法来干预对象生成的过程,例如:
如上所示,重写的构造方法中定义了3个参数,分别对应圆形的半径和圆心点X、Y坐标。在使用Circle类构造对象时,需要将指定的参数传入,例如:
var circle = new Circle(6,1,1);//使用参数构造圆形对象
有一点需要额外注意,一旦重写了构造方法,默认的无参构造方法将不再可用。在上面的构造方法中,this关键字指的就是当前实例对象,构造方法的实质是将对象属性的赋值过程由外界封装到类的内部。
在Dart中,关于构造方法的编写还有一个小技巧,一般情况下,我们可以直接将构造方法定义成如下模样,Dart会自动进行参数和属性的匹配,进行赋值,非常方便。
有时,一个类需要有多个构造方法,比如自定义的圆形类,很多时候需要快速创建出单位圆(圆心为坐标原点、半径为1的圆)。这时,就可以定义一个便捷的构造方法帮助我们直接生成单位圆,这类构造方法也被称为命名构造方法,示例如下:
命名构造方法通常用来快速地创建标准对象,同样,命名构造方法也可以有参数,并且只要参数名与类中定义的属性名一致,也可以使用Dart自动匹配赋值的特性。
在Dart中,类还有一个强大的功能是支持继承,关于继承的内容后面会详细介绍,但是这里你需要牢记,构造方法不会被继承。
3.2.2 实例方法
类封装了属性和方法,属性用来存储描述类的数据,方法用来描述类的行为。在面向对象编程中,生活中的事物都可以模拟成程序中的对象,例如一个教务系统软件中一定有教师相关信息,每一位教师都是一个教师对象,可以创建教师类来描述教师对象,示例代码如下:
上面的代码为教师类添加了3个属性,name属性用来描述教师的名字,number属性用来描述教师的编号,subject属性用来描述教师教学的课程。除了属性外,还为教师对象添加了sayHi方法与teaching方法,方法的用法和函数一样,只是在调用时需要用对象来调用,并且方法中会自动将当前对象绑定到this关键字上。也就是说,在方法中可以通过this关键字获取对象的属性信息,也可以调用其他方法。方法也需要通过点语法来进行调用,例如:
类中还有两个非常特殊的方法:Setters方法与Getters方法。Setters方法用来设置对象属性,Getters方法用来获取对象属性。其实当我们使用点语法访问对象属性信息时,调用的就是Setters方法或Getters方法,在定义属性时,Dart会自动生成默认的Setters方法和Getters方法。Setters方法和Getters方法的另一大作用是定义附加属性,附加属性也可以理解为计算属性,即这些数据通常不是描述对象的最原始数据,而是通过计算得来的,例如:
上面的代码中,description就是附加属性,其并没有真正占用内存空间进行存储,而是通过其他属性计算而来的。
3.2.3 抽象类与抽象方法
抽象类是面向对象开发中较为难理解的一点。在Dart中,抽象类中可以定义抽象方法。所谓抽象方法,是指只有定义却没有实现的方法,抽象是面向接口开发的基础。以生活中汽车产品的生产为例,一辆完整的汽车的生产往往需要多个厂家合作,例如发动机生产厂家、轮胎生产厂家、门窗内设生产厂家等。不同的厂家生产的配件若要完美地组合成一辆汽车,则必须遵守统一的标准,也可以理解为按照实现的协议进行生产。在编程中也是这样的,一个复杂的程序可能需要很多开发者甚至多个部门进行配合开发,每个开发者或部门负责一个模块,而模块之间又可以进行交互与连通,这时在程序真正编写前,我们就需要先约定协议、制定接口。
现在你应该理解了,抽象类实际上就是一个接口,接口中定义了未实现的方法告诉调用者:如果有类实现了这个接口,这个类就拥有接口所描述的功能。例如,我们可以为教师类定义一个接口,示例如下:
上面的TeacherInterface接口中只定义了一个抽象方法,Teacher类可以对这个接口进行实现,示例代码如下:
一个类也可以同时实现多个接口,例如再定义一个人类接口,示例如下:
抽象类不可以被实例化,即不能直接使用抽象类来构造实例对象,只能通过实现这个抽象类接口的类或者继承它的子类来实例化对象。关于继承的内容,后面会介绍。
3.2.4 类的继承
继承是类的重要特性。子类继承父类后,可以直接使用父类中定义的属性和方法,并且子类可以对父类的方法进行重写以实现定制化的功能。继承其实很容易理解,现实中的事物为了方便描述与归纳,也会进行分门别类,例如生物界可以分为动物和植物,动物类下面又可以分出鱼类、鸟类等,动物类就是生物的子类,鱼类、鸟类又是动物类的子类。越是上层的类,封装的属性和方法越通用,子类会在父类的基础上进行扩展,增加许多独特的属性和方法。
在Dart中,使用extends关键字进行类的继承。以教师类为例,我们可以定义一个人类作为其父类,示例如下:
如上面的代码所示,Teacher类直接继承了People类的姓名、年龄属性和sayHi方法。但是需要注意,构造方法是不会被继承的,在Teacher类中可以使用super关键字来调用父类的方法,包括构造方法。子类也可以重载父类的方法,并且在重载时可以调用对应的父类方法,例如:
其中,@override关键字可以省略,这个关键字的作用仅仅是标注这个方法是子类重载父类的。
3.2.5 运算符重载
我们前面在学习运算符相关内容时了解到,Dart中的运算符非常灵活,例如加法运算符除了可以用在数值的加法运算外,在字符串对象间使用也可以实现字符串的拼接功能。Dart中的运算符之所以如此灵活,是由于Dart是一门完全面向对象的语言,而运算符的运算实质是方法的调用。因此,我们也可以为自定义的类添加运算符方法,例如:
上面的代码定义了一个尺寸类Size,类中定义了宽度与高度两个属性,operator关键字用来进行运算符的重载,其格式如下:
上面的代码重载了Size类的加法运算,当将两个Size对象进行相加时,分别将它们的宽度和高度进行相加,并将新的对象返回。
重载运算符非常简单,却是非常强大的一个功能,在Dart中支持重载的运算符如表3-1所示。
表3-1 支持重载的运算符
3.2.6 noSuchMethod方法
对于非抽象类,当定义了一个没有实现的方法时,代码的运行会产生异常,例如:
运行上面的代码,控制台会抛出如下的异常信息:
其实,如果一个类实现了某个接口或者继承了某个抽象类,却没有全部实现接口和抽象类中声明的方法,就会产生如上的异常。然而实际开发中,接口或抽象类中的方法有时并不需要全部实现,这时可以选择重载noSuchMethod方法,如果重载了这个方法,当对象调用到这些未实现的方法时,就会执行noSuchMethod方法,例如:
需要注意,虽然重载noSuchMethod方法可以避免调用未定义方法异常的产生,但是其也会掩盖代码逻辑中的错误,在实际开发时,要尽量少使用这种方式。
3.2.7 枚举类型
枚举是一种特殊的类型,其用来描述有限个数的数据集合。比如前面在定义教师类时,其中定义了一个科目的属性,虽然我们将其定义为字符串类型,但是这并不十分严谨,教师所教学科目的类目是有限的,而且应该是固定的,不会随意增减,对于这种情况,使用枚举非常合适。
enum关键字用来定于枚举,其实枚举与数组类似,其中的数据也都有下标,从0开始,例如:
也可以使用values属性来获取枚举中所有的值,例如:
更多时候,枚举会和多分支语句结合使用,示例代码如下:
3.2.8 扩展类的功能——Mixin特性
Mixin是Dart中非常强大的一个特性。通过前面的学习,我们知道,Dart只支持单继承,即子类只能够有一个父类。有的时候,我们需要集合多个类的功能来实现一个复杂的自定义类,就需要使用到Mixin特性。
Mixin从字面来理解为混合的意思,顾名思义,Mixin的主要作用也是进行混合,其允许一个类将其他类的功能混合进来,例如:
从控制台的打印信息可以看出,Bird类已经成功混合了Descript类中的功能,可以调用其中定义的方法。
能够进行混合的类被称为Mixin类,Mixin类中不能实现构造方法,否则不能够被其他类进行混合。使用with关键字来进行Mixin混合,Mixin支持多混合,例如:
还有一点需要额外注意,作为Mixin的类虽然不能够定义构造方法,但是可以使用默认的构造方法进行实例化,如果不想使Mixin类实例化,那么可以使用mixin关键字代替class关键字来定义Mixin类,示例如下:
使用mixin定义的Mixin类不能够被继承,也不能够进行实例化。Mixin类本身可以进行继承,如果使用class关键字进行定义,就和普通类的集成语法一致,如果使用mixin关键字进行定义,就使用on关键字进行继承,例如:
提示
mixin关键字在Dart 2.1版本之后可用。
学习了Mixin的基本概念与简单用法,下面我们需要更深入地了解Mixin的工作原理,首先观察下面的示例:
上面的代码使用到了继承和多混合,并且子类、父类、Mixin类中都对相同的方法进行了实现,运行代码,控制台会打印出“sub func”。可以看出,无论是Mixin还是继承,子类中的方法实现优先级都是最高的,将子类实现的方法去掉,再次运行,控制台将输出“two func”,这说明Mixin中方法的优先级要高于父类中方法的优先级,并且在多混合中,后混合的优先级更高。
因此,在对于继承和混合一起使用的复杂场景中,你需要牢记如下两个原则:
(1)当前类中方法的优先级最高。
(2)Mixin中方法的优先级高于继承父类方法的优先级,并且在混合时,Mixin从左到右优先级依次增高。
3.2.9 类属性与类方法
前面我们定义类时定义的属性和方法都是针对实例的,即由类的实例对象进行访问或调用。其实,类本身也是一种对象,在类中也可以定义类属性与类方法,使用类名直接进行访问和调用。示例代码如下:
类属性也被称为类静态属性,其通常用来存放某些固定的且在类的所有实例中共享的属性,类方法也被称为类静态方法,通常会提供一些静态的计算功能。