Dart面向对象编程(OOP)是一种将代码组织成对象的编程范式,每个对象都包含数据和行为,类似于现实世界中的实体。
1 面向对象
- 面向对象编程OOP的三个基本特征
- 封装:把客观事物封装成抽象的类,并把自己的部分属性和方法提供给其他对象。
- 继承:可使用现有类的功能,并在无需重新编写原来类的情况下对功能进行扩展。
- 多态:允许将子类类型的指针赋值给父类类型的指针,同一个函数调用会有不同的执行效果。
2 类的创建
- Dart中一切皆对象,所有的对象都继承自Object类,一个类通常由属性和方法组成。
// Person类的创建
class Person {
// 类的属性
String name = '张三';
int age = 21;
// 类的方法
void getInfo() {
print('$name --- $age');
print('${this.name} --- ${this.age}');
}
void setInfo(int age) {
this.age = age;
}
}
void main() {
// 实例化Person类
var p1 = new Person();
// 调用属性
print(p1.name);
// 调用方法
p1.getInfo();
Person p2 = new Person();
// print(p2.name);
p2.setInfo(22);
p2.getInfo();
}
3 构造函数
- Dart中的4种构造函数
- 普通构造函数:
ClassName(...)
,分为无参构造函数和有参构造函数。 - 命名构造函数:
ClassName.identifier(...)
,支持一个类实现多个构造函数,不能重载。 - 常量构造函数:
const ClassName(...)
,如果生成类的对象不变,可以定义常量构造函数。 - 工厂构造函数:
factory ClassName(...)
,factory关键字可以放在类名函数和命名函数前。
3-1 无参构造函数
- 如果不声明构造函数,则Dart会提供一个默认的无参构造函数。
- 若自定义有参构造函数,那么默认的无参构造函数就不存在了。
class Person {
String name = '张三';
int age = 21;
// 构造函数,该方法在实例化时触发
Person() {
print('无参构造函数');
}
void printInfo() {
print('${this.name} --- ${this.age}');
}
}
void main() {
// 实例化
Person p = new Person();
// p.printInfo();
}
3-2 有参构造函数
- 实现的功能:在构造函数中,可以动态地给类指定属性。
class Person {
// 有参构造函数,提供语法糖简化
String name = '';
int age = 0;
// 自定义构造函数
// Person(String name, int age) {
// this.name = name;
// this.age = age;
// }
// 构造函数的简写
Person(this.name, this.age);
void printInfo() {
print('${this.name} --- ${this.age}');
}
}
void main() {
// 实例化
Person p1 = new Person('张三', 21);
p1.printInfo();
Person p2 = new Person('李四', 22);
p2.printInfo();
}
3-3 命名构造函数
class Person {
// 空安全机制,默认情况下所有变量都必须在声明时被初始化,不能为null
String name = '';
int age = 0;
// 命名构造函数
Person.now() {
print('命名构造函数');
}
Person.setInfo(String name, int age) {
this.name = name;
this.age = age;
}
void printInfo() {
print('${this.name} --- ${this.age}');
}
}
void main() {
Person p1 = new Person.now();
Person p2 = new Person.setInfo('张三', 21);
p2.printInfo();
// 实例化DateTime调用它的命名构造函数
var d = new DateTime.now();
print(d);
}
4 类模块化
- 将创建的类放在单独的文件夹下,例如:Person类,放在/lib/Person.dart中。
class Person {
String name = '';
int age = 0;
Person(this.name, this.age);
Person.now() {
print('命名构造函数');
}
Person.setInfo(String name, int age) {
this.name = name;
this.age = age;
}
void printInfo() {
print('${this.name} --- ${this.age}');
}
}
- 在另一个文件中调用该Person类,则需要导入该类的类文件名。
import './lib/Person.dart';
void main() {
Person p = new Person.setInfo('张三', 21);
p.printInfo();
}
4-1 私有属性
- 在Dart中没有public、private protected等访问修饰符,但可以使用
_
将属性和方法私有化。 - 自定义私有属性,或自定义私有方法时,必须将其单独抽类到一个文件中,这样才有效果。
- 例如:Animal类,定义name为私有属性,将创建的Animal类放入/lib/AnimalProperties.dart。
class Animal {
// 私有属性name
String _name;
int age;
Animal(this._name, this.age);
void printInfo() {
print('${this._name} --- ${this.age}');
}
// 公有方法
String getName() {
// 私有属性name只在当前类中使用
return this._name;
}
}
- name私有属性只在当前的Animal类中使用,此时main()函数无法访问Animal类中的私有属性name。
- name私有属性在Animal类的getName()公有方法中,可通过访问公有方法间接地使用私有属性name。
import './lib/AnimalProperties.dart';
void main() {
Animal a = new Animal('小狗', 100);
// 无法访问私有属性
// print(a._name);
print(a.age);
// 访问公有方法来访问私有属性
print(a.getName());
}
4-2 私有方法
- 例如:Animal类,定义run()为私有方法,将创建的Animal类放在/lib/AnimalMethods.dart中。
class Animal {
String name;
int age;
Animal(this.name, this.age);
// 私有方法
void _run() {
print('私有方法');
}
// 公有方法
execRun() {
// 类里面方法的相互调用
this._run();
}
}
- run()私有方法在Animal类的execRun()公有方法中,可通过访问公有方法间接地访问私有方法run()。
import './lib/AnimalMethods.dart';
void main() {
Animal a = new Animal('小狗', 100);
// 间接地调用私有方法
a.execRun();
}
4-3 getter与setter
class Rect {
num height;
num width;
Rect(this.height, this.width);
get area {
return this.height * this.width;
}
set areaHeight(value) {
this.height = value;
}
}
void main() {
Rect r = new Rect(10, 3);
// 通过访问属性来访问area
print('面积:${r.area}');
r.areaHeight = 6;
print('面积:${r.area}');
}
4-4 类的初始化列表
- 可以在构造函数体运行之前初始化实例变量。
class Rect {
int height;
int width;
// 初始化实例变量
Rect(): height = 30, width = 10 {
print('长为:${this.height}\n宽为:${this.width}');
}
getArea() {
return this.height * this.width;
}
}
void main() {
Rect r = new Rect();
print('面积:${r.getArea()}');
}
4-5 类中的静态成员
- 通过使用static关键字,来实现类级别的变量以及函数。
- 静态属性与静态方法通过类名访问,而非类的实例访问。
class Person {
static String name = '张三';
static void show() {
print(name);
}
}
main() {
// var p = new Person();
// p.show();
print(Person.name);
// 调用静态方法
Person.show();
}
4-6 静态非静态比较
- 非静态方法可以直接访问静态成员、静态方法,以及非静态成员。
- 静态方法能访问静态成员和静态方法,不能访问非静态成员和非静态方法。
class Person {
static String name = '张三';
int age = 21;
static void show() {
print(name);
}
void printInfo() {
// 访问非静态属性
print(this.age);
// 访问静态属性
print(name);
// 调用静态方法
show();
}
static void printUserInfo() {
// 访问静态属性
print(name);
// 调用静态方法
show();
// 无法访问非静态属性
// print(age);
// print(this.age);
// 无法访问非静态方法
// printInfo();
// this.printInfo();
}
}
main() {
Person p = new Person();
p.printInfo();
Person.printUserInfo();
}
5 类的继承
- 子类使用extends关键词来继承父类,子类能复写父类的方法。
- 子类会继承父类里面可见的属性和方法,但不会继承构造函数。
class Person {
String name = '张三';
num age = 21;
void printInfo() {
print('${this.name} --- ${this.age}');
}
}
// 可以访问Person中共有的属性和方法
class Web extends Person {}
main() {
// 实例化Web类
Web w = new Web();
print(w.name);
w.printInfo();
}
5-1 super关键词
class Person {
String name;
num age;
Person(this.name, this.age);
void printInfo() {
print('${this.name} --- ${this.age}');
}
}
class Web extends Person {
// super表示实例化子类时,将传过来的name和age参数赋值给父类Person
Web(String name, num age): super(name, age) {}
}
main() {
Web w = new Web('张三', 21);
w.printInfo();
}
5-2 子类扩展属性和方法
class Person {
String name;
num age;
Person(this.name, this.age);
void printInfo() {
print('${this.name} --- ${this.age}');
}
}
class Web extends Person {
// 定义子类自己的属性
String sex;
Web(String name, num age, this.sex): super(name, age);
// 定义子类自己的方法
run() {
print('${this.name} --- ${this.age} --- ${this.sex}');
}
}
main() {
Web w = new Web('张三', 21, '男');
w.printInfo();
w.run();
}
5-3 给命名构造函数传参
class Person {
String name;
num age;
Person(this.name, this.age);
// 命名构造函数
Person.construct(this.name, this.age);
void printInfo() {
print('${this.name} --- ${this.age}');
}
}
class Web extends Person {
String sex;
Web(String name, num age, this.sex): super.construct(name, age);
run() {
print('${this.name} --- ${this.age} --- ${this.sex}');
}
}
main() {
Web w = new Web('张三', 21, '男');
w.printInfo();
w.run();
}
5-4 子类覆写父类的方法
class Person {
String name;
num age;
Person(this.name, this.age);
void printInfo() {
print('${this.name} --- ${this.age}');
}
work() {
print('${this.name}在工作');
}
}
class Web extends Person {
Web(String name, num age): super(name, age);
// 子类中扩展方法
run() {
print('run');
}
// 覆写父类的方法,不加@override也可以,建议加上
@override
void printInfo() {
print('姓名:${this.name} --- 年龄:${this.age}');
}
@override
work() {
print('姓名:${this.name} --- 工作:写代码');
}
}
main() {
// 实例化子类
Web w = new Web('张三', 21);
w.printInfo();
w.work();
}
5-5 子类中调用父类方法
class Person {
String name;
num age;
Person(this.name, this.age);
void printInfo() {
print('${this.name} --- ${this.age}');
}
work() {
print('${this.name}:工作 --- ');
}
}
class Web extends Person {
Web(String name, num age): super(name, age);
// 子类中扩展方法
run() {
print('程序:执行 --- ');
// 子类调用父类方法
super.work();
}
@override
void printInfo() {
print('姓名:${this.name} --- 年龄:${this.age}');
}
}
main() {
// 实例化子类
Web w = new Web('张三', 21);
w.printInfo();
w.run();
}
6 抽象类与泛型
- 抽象类主要通过abstract关键词用于定义标准,子类可继承抽象类,也可实现抽象类接口。
- 抽象类不能被实例化,只有继承它的子类可以。
- 如果子类继承抽象类,必须实现它的抽象方法。
- 没有方法体的方法称之为抽象方法,不能用abstract关键词来声明。
- 若把抽象类当接口实现,须先实现抽象类中定义的所有属性和方法。
- extends抽象类和implements的区别
- 如果只是把抽象类当做标准,那么就用implements关键词来实现抽象类。
- 若要复用抽象类中的方法且用抽象方法约束子类,用extends继承抽象类。
// 定义一个Animal类,要求它的子类必须包含eat方法
abstract class Animal {
// 抽象方法
eat();
// 非抽象方法
printInfo() {
print('抽象类中的普通方法');
}
}
class Dog extends Animal {
@override
eat() {
print('小狗在吃美味的骨头');
}
}
main() {
Dog d = new Dog();
d.eat();
d.printInfo();
// 抽象类没法直接被实例化,只有继承它的子类才能被实例化
// var a = new Animal();
}
6-1 多态
- 多态就是父类定义一个方法不去实现,让继承它的子类去实现,每个子类有不同的表现。
abstract class Animal {
// 抽象方法
eat();
}
class Dog extends Animal {
@override
eat() {
print('小狗在吃美味的骨头');
}
}
class Cat extends Animal {
@override
eat() {
print('小猫在吃可爱的老鼠');
}
}
main() {
Dog d = new Dog();
d.eat();
Cat c = new Cat();
c.eat();
}
6-2 赋值
- 允许将子类类型的指针赋值给父类类型的指针,同一个函数调用会有不同的执行效果。
abstract class Animal {
// 抽象方法
eat();
}
class Dog extends Animal {
@override
eat() {
print('小狗在吃美味的骨头');
}
run() {
print('程序正在执行过程中');
}
}
class Cat extends Animal {
@override
eat() {
print('小猫在吃可爱的老鼠');
}
run() {
print('程序正在执行过程中');
}
}
main() {
// 子类的实例赋值给父类的引用
Animal d = new Dog();
d.eat();
Animal c = new Cat();
c.eat();
}
6-3 接口
- Dart的接口没用interface关键字定义,普通类或抽象类都可作为接口,使用implements关键字实现。
- 如果实现接口的类是普通类,那么Dart会将普通类和抽象类里面属性的方法全部覆写一遍。
- 抽象类可定义抽象方法,普通类不可,要实现类似于Java接口的方式,一般都会用抽象类。
- 建议使用抽象类定义接口。
// 接口,即约定或叫规范,定义一个DB库,支持MySQL
abstract class DB {
// 数据库的链接地址
String uri = '';
add(String data);
edit();
delete();
}
class MySQL implements DB {
@override
String uri;
MySQL(this.uri);
@override
add(String data) {
print('这是MySQL的add方法:$data');
}
@override
edit() {
return null;
}
@override
delete() {
return null;
}
}
main() {
MySQL mysql = new MySQL('127.0.0.1');
mysql.add('123456789');
}
6-4 抽类
- 抽类步骤一:lib目录新建一个DB.dart文件。
// 接口,即约定或叫规范,DB.dart文件
abstract class DB {
// 数据库的链接地址
String uri = '';
add(String data);
edit();
delete();
}
- 抽类步骤二:lib目录新建一个MySQL.dart文件,引入DB.dart文件。
import 'DB.dart';
// MySQL.dart文件
class MySQL implements DB {
@override
String uri;
MySQL(this.uri);
@override
add(String data) {
print('这是MySQL的add方法:$data');
}
@override
edit() {
return null;
}
@override
delete() {
return null;
}
}
- 抽类步骤三:创建一个Test.dart文件,用于引入MySQL.dart文件。
import 'lib/MySQL.dart';
// Test.dart文件
main() {
MySQL mysql = new MySQL('127.0.0.1');
mysql.add('123456789');
}
6-5 多接口
// 一个类实现多个接口
abstract class A {
String name = '';
printA();
}
abstract class B {
printB();
}
class C implements A, B {
@override
String name = '';
@override
printA() {
print('打印A');
}
@override
printB() {
print('打印B');
}
}
void main() {
C c = new C();
c.printA();
c.printB();
}
6-6 mixins
- mixins即混入,指在类中混入其他功能,在Dart中可以使用mixins实现类似多继承的功能。
- 使用的条件伴随着Dart版本的变化一直在改变,这里用的是Dart 2.x中使用mixins的条件。
- 一个类可以mixins多个mixins类,mixins绝不是继承,也不是接口,而是一种全新的特性。
class A {
String info = '这是A';
void printA() {
print('A');
}
}
class B {
void printB() {
print('B');
}
}
class C with A, B {}
void main() {
var c = new C();
c.printA();
c.printB();
print(c.info);
}
(1) 继承自Object
- 作为mixins的类只能继承自Object,不能继承其他类。
class Person {
printInfo() {
print('Person类');
}
}
class A extends Person {
String info = '这是A';
void printA() {
print('A');
}
}
class B {
void printB() {
print('B');
}
}
// 这里A类继承了Person类,C类mixinsA类时会报错
// class C with A, B {}
void main() {}
(2) 不能有构造函数
- 作为mixins的类不能有构造函数。
class A {
String info = '这是A';
void printA() {
print('A');
}
}
class B {
// 写构造函数会报错
// B() {};
void printB() {
print('B');
}
}
class C with A, B {}
void main() {
var c = new C();
c.printA();
c.printB();
print(c.info);
}
(3) 继承与mixins一起
- 继承与mixins一起写的方法。
class Person {
String name;
Object age;
Person(this.name, this.age);
printInfo() {
print('${this.name} --- ${this.age}');
}
}
class A {
String info = '这是A';
void printA() {
print('A');
}
}
class B {
void printB() {
print('B');
}
}
// C类具有Person类、A类和B类的功能
// class C with Person, A, B {}
// 此时Person类中可以有构造函数
class C extends Person with A, B {
C(String name, Object age): super(name, age);
}
void main() {
var c = new C('张三', 21);
c.printInfo();
}
(4) 顺序不同结果不同
- 注意
with A, B
或with B, A
的顺序,打印出来的内容不一样,后面替换前面。
class Person {
String name;
Object age;
Person(this.name, this.age);
printInfo() {
print('${this.name} --- ${this.age}');
}
// 继承与mixins中有同样的方法,with A, B或with B, A的顺序同下
void run() {
print('Person类执行中');
}
}
class A {
String info = '这是A';
void printA() {
print('A');
}
void run() {
print('A执行中');
}
}
class B {
void printB() {
print('B');
}
void run() {
print('B执行中');
}
}
// 注意with A, B或with B, A的顺序,打印内容不一样,后面替换前面
class C extends Person with A, B {
C(String name, Object age): super(name, age);
}
void main() {
var c = new C('张三', 21);
c.run();
}
(5) mixins的实例类型
- mixins的实例类型就是其超类的子类型。
// ignore_for_file: unnecessary_type_check
class A {
String info = '这是A';
void printA() {
print('A');
}
}
class B {
void printB() {
print('B');
}
}
class C with A, B {}
void main() {
var c = new C();
print(c is A);
print(c is B);
print(c is C);
var a = new A();
print(a is Object);
}
6-7 泛型方法
- 泛型即解决类、接口、方法的复用性,以及对不特定数据类型的支持(类型校验)。
// 只能返回String类型的数据
String getData1(String value) {
return value;
}
// 同时支持返回String类型和int类型,代码冗余
String getData2(String value) {
return value;
}
int getData3(int value) {
return value;
}
// 同时返回String类型和number类型,不指定类型可以解决问题
getData4(value) {
return value;
}
// 不指定类型放弃了类型检查,需要实现的是传入什么就返回什么
T getData5<T>(T value) {
return value;
}
void main() {
print(getData5(21));
print(getData5('张三'));
getData5<int>(22);
getData5<String>('李四');
getData5<int>(23);
getData5<String>('王五');
}
6-8 泛型类定义
class MyList1 {
List list = <int>[];
void add(int value) {
this.list.add(value);
}
List getList() {
return list;
}
}
// 定义泛型类
class MyList2<T> {
List list = <T>[];
void add(T value) {
this.list.add(value);
}
List getList() {
return list;
}
}
main() {
// 创建固定长度的List,这里使用到了泛型
List l1 = new List<String>.filled(2, '');
l1[0] = '121';
l1[1] = '张三';
print(l1);
// new关键词可以省略
MyList1 l2 = MyList1();
l2.add(123);
l2.add(321);
print(l2.getList());
MyList2 l3 = MyList2();
l3.add(125);
l3.add('李四');
print(l3.getList());
MyList2 l4 = MyList2<String>();
l4.add('王五');
// 传int会报错
l4.add('135');
print(l4.getList());
}
6-9 泛型接口定义
- 实现数据缓存的功能:文件缓存和内存缓存,这两种功能按接口约束实现。
- 定义一个泛型接口
- 约束实现子类必须有
getByKey(Key)
和setByKey(key, value)
。 - 要求setByKey的value类型和实例化子类时指定的类型需保持一致。
- 约束实现子类必须有
// 定义一个接口
abstract class Cache<T> {
// 定义两个抽象方法
getByKey(String key);
void setByKey(String key, T value);
}
// 定义两个类FileCache和MemoryCache
class FileCache<T> implements Cache<T> {
@override
getByKey(String key) {
return null;
}
@override
void setByKey(String key, T value) {
print('我是文件缓存,把key=${key}、value=${value}的数据写入到了文件中');
}
}
class MemoryCache<T> implements Cache<T> {
@override
getByKey(String key) {
return null;
}
@override
void setByKey(String key, T value) {
print('我是内存缓存,把key=${key}、value=${value}的数据写入到了内存中');
}
}
void main() {
MemoryCache m1 = new MemoryCache<String>();
m1.setByKey('index', '{姓名: 李四, 工作: 测试}');
MemoryCache m2 = new MemoryCache<Map>();
m2.setByKey('index', {'姓名': '张三', '工作': '码农'});
}