目录
前言
本篇继续来学习Dart语言的内容,今天接上一篇《Dart语言之常用数据类型》来介绍Dart里面的面向对象。面向对象的核心特性包括封装、继承和多态。
1、初始化列表和标准构造方法
类是面向对象编程的核心概念,包含对象、变量和方法。在Dart中定义类时,所有类默认继承自Object。
定义类变量的语法为:在类的大括号内直接声明变量类型和变量名,构造方法用于初始化类的对象。多态的表现形式包括方法重载,例如重写父类的toString方法,举个栗子如下:
Dart
/// 定义一个类,所有的类都继承自Object
class Person {
String name;
int age;
/// 标准的构造方法
Person(this.name, this.age);
/// 重写父类方法
@override
String toString() {
return '$name,$age';
}
}
初始化列表位于构造方法冒号后的表达式,用于初始化类的变量。
私有变量通过下划线标识,仅在当前文件内可访问:String _school;
可选参数通过大括号声明:Student({String city});
默认参数在可选参数后通过等号设置默认值,例如:Student({String country = 'China'});
可选参数和默认参数不仅适用于构造方法,也适用于其他类型的方法。
初始化列表可在子类构造方法体之前设置,用于初始化实例变量。若父类没有无参构造方法,则需在初始化列表中调用父类的构造方法。构造方法的方法体可通过大括号设置,但非必须。示例:
Dart
class Student extends Person {
// 定义类的变量
String? _school; // 私有变量通过下划线标识,仅在当前文件内可访问
String? city;
String? country;
late String name;
/// 构造方法:
/// 通过this._school初始化自有参数
/// name,age交给父类进行初始化
/// city为可选参数
/// country为默认参数
Student(
this._school,
String name,
int age, {
this.city,
this.country = '中国',
}) : // 初始化列表可在子类构造方法体之前设置,用于初始化实例变量。例如:
name = '$country-$city',
// 若父类没有无参构造方法,则需要在初始化列表中调用父类的构造方法
super(name, age) {
// 构造方法体不是必须的
print('构造方法的方法体不是必须的');
}
}
2、命名构造方法
命名构造方法是开发过程中常见的一种构造方法形式。命名构造方法以类名开头,通过点符号连接自定义方法名,用于实现类的多构造方法功能。
Dart
// 命名构造方法【类名+.+方法名】
Student.convert(Student stu) : super(stu.name, stu.age);
3、工厂构造方法
在 Dart 中,使用 factory 关键字在构造方法前定义一个工厂构造方法。它允许你在构造函数中执行自定义逻辑,比如返回一个已存在的实例(实现单例模式)、返回子类实例,或者根据条件返回不同类型的实例。与普通构造函数不同,工厂构造方法需要显式使用 return 关键字来返回一个对象,并且不一定要返回新的实例。
定义工厂构造方法:
- 使用
factory关键字:在类定义中,在构造方法的声明前加上factory关键字。 - 实现逻辑:在方法体内部编写创建和返回对象的逻辑。
- 使用
return关键字:工厂构造方法必须使用return关键字返回一个对象。
Dart
class Singleton {
static Singleton? _instance; // 静态变量,缓存实例
// 私有命名构造函数
Singleton._internal();
// 工厂构造方法
factory Singleton() {
if (_instance == null) {
// 如果实例不存在,就创建一个新的实例
_instance = Singleton._internal();
}
// 返回缓存的实例
return _instance!;
}
}
void main(){
var s1 = Singleton();
var s2 = Singleton();
print(s1 == s2); // 输出:true
}
4、命名工厂构造方法
命名工厂构造方法的主要形式如下:
- 通过factory关键字标识
- 类名加点加自定义方法名构成
- 参数可根据需要设置
- 可返回构造方法并根据需要进行初始化
- 无需将类final变量作为参数,是一种灵活的获取类对象的方式
Dart
// 命名工厂构造方法:factory [类名+.+方法名]
// 它可以有返回值,而且不需要将类的final变量作为参数,是一种灵活获取类对象的方式
factory Student.stu(Student stu){
return Student(stu._school, stu.name, stu.age);
}
5、set&get&静态方法
set方法用于修改私有字段值,需使用set关键字声明:
Dart
// 可以为私有字段设置setter来控制外界对私有字段的修改
set school(String value) {
_school = value;
}
get方法用于为私有字段设置访问接口,允许外部代码获取私有字段值:
Dart
// 可以为私有字段设置getter来让外界获取到私有字段
String? get school => _school;
静态方法使用static关键字标识,属于类而非实例,调用方式为类名.方法名(),无需创建类实例:
Dart
// 静态方法
static doXXX(String str) {
print('doXXX:$str');
}
6、抽象类
抽象类的定义需使用abstract关键字修饰。例如,定义Study抽象类时需标注为abstract class Study。抽象类的作用主要体现在接口定义场景。
Dart
/// 使用abstract来定义抽象类,该类不能被实例化,在定义接口时非常有用
abstract class Study{
void study(); // 抽象方法
@override
String toString() {
return '抽象类中的普通方法';
}
}
/// 继承抽象类要实现它的抽象方法,否则也需要将自身定义成抽象类
class StudySon extends Study{
@override
void study() {
// TODO: implement study
}
}
7、mixins
mixin的特征包括:
- 必须创建继承自object的子类
- 不能继承其他类
- 不能声明构造方法
- 不能调用super
Dart
mixin class OldMan{
void workOld(){
print('OldMan');
}
}
mixin class YoungMan{
void workYoung(){
print('YoungMan');
}
}
class Worker with OldMan,YoungMan{
@override
void workOld() {
// TODO: implement workOld
super.workOld();
}
@override
void workYoung() {
// TODO: implement workYoung
super.workYoung();
}
}
class Worker extends OldMan with YoungMan{
@override
void workOld() {
// TODO: implement workOld
super.workOld();
}
@override
void workYoung() {
// TODO: implement workYoung
super.workYoung();
}
}
8、方法
①、方法的完整结构包含以下要素:
- 返回值:定义方法执行后的输出类型,可为void或具体数据类型
- 参数:接收外部输入的变量集合
- 方法名:方法的唯一标识符(匿名方法除外)
其中,返回值特性包括:
- 类型可缺省,默认返回void类型
- 需显式声明返回具体数据类型时,必须包含return语句
- 匿名方法作为特例可不声明方法名
以及,方法参数具有以下特征:
- 参数类型与参数名共同构成参数声明
- 参数分类:
- 可选参数
- 默认参数
- 构造方法的参数规则完全适用于普通方法
②、私有方法的核心特征:
- 命名规范:以下划线开头
- 作用域限制:仅在定义文件中可访问
应用范围:同样适用于私有字段和私有类
③、匿名方法的定义:
- 无方法名的即时函数定义
常见应用场景:作为回调函数参数
Dart
class FunctionLearn {
int sum(int value1, int value2) {
return value1 + value2;
}
_learn() {
print('私有方法');
}
anonymousFunction() {
const list = ['func1', 'func2'];
list.forEach((item) {
print('item: $item');
});
}
}
关于其它类型的方法在上面基本上都已经介绍了,这里不再赘述。
9、泛型
最后来看一下泛型的使用,泛型的核心价值在于提升代码复用性,支持对非特定数据类型的通用操作。在 Dart 中,通过在类名后添加一对尖括号 <T> 来定义泛型类,其中 T 是一个泛型占位符,表示一个不确定的类型。如果需要多个泛型参数,可以使用逗号分隔,例如 <T, E>。泛型类可以在实例化时指定具体的类型,以提高代码的复用性和类型安全。
示例代码如下:
Dart
class Cache<T> {
final Map<String, T> _cached = <String, T>{};
void setItem(String key, T value) {
_cached[key] = value;
}
T? getItem(String key) {
return _cached[key];
}
}
void main() {
var ca = Cache<String>();
ca.setItem('key', '123');
print(ca.getItem('key'));
}
关键点:
- 定义泛型类 :在类名后面使用尖括号
<T>来声明泛型参数。 - 使用泛型参数 :在类的方法、属性等中使用泛型参数
T来表示一个不确定的类型。 - 实例化 :在创建类的实例时,通过
<具体类型>指定泛型参数的类型,例如Cache<int>()。 - 多个泛型参数 :可以使用逗号分隔多个泛型参数,例如
class MyMap<K, V>。 - 类型约束 :可以使用
extends关键字来限定泛型参数的类型,例如class NumericCache<T extends num>表示T必须是num或其子类。
OK,到这里今天的内容就介绍完了,更多详情请参考文档:https://dart.dev/docs
下期再会,祝:工作顺利!