九: Flutter之Dart第六节(类和对象)?
Dart 是一个面向对象的语言、面向对象中非常重要的概念就是类、类产生了对象。
这一节,我们就具体来学习类和对象、但是Dart对类进行了很多其他语言没有的特性、一起探讨。9.1: 类的定义
在Dart中、定义类用class关键字。
类通常有两部分组成:成员(member) 和 方法 (method)。
定义类的伪代码如下:class 类名 { 类型 成员名 返回值类型 方法名(参数列表) { 方法体 } }
编写一个简单的Person类
- 这里有一个注意点:我们在方法中使用属性(成员变量/实例变量)时、并没有加this;
- Dart的开发风格中、在方法中通常使用属性时、会省略this、但是有命名冲突时、this不能省略。
class Person { String name; eat() { print('$name在吃东西'); } } main(List<String> args) { // 2: 我们在方法中使用属性并没有使用this、一般我们都是省略的、但是如果在方法中使用属性的时候、如果有命名 // 冲突的话、this不能省略 // 1: 创建类的对象, 直接使用Person() 也可以创建 var p = new Person(); // 2: 给对象的属性赋值 p.name = 'lishengbing'; // 3: 调用对象方法 p.eat(); //打印: lishengbing在吃东西 }
9.2: 构造方法
9.2.1: 普通构造方法
我们知道、当通过类创建一个对象时、会调用这个类的构造方法
当类中没有明确指定的构造方法时、将默认拥有一个无参的构造方法
前面的Person中我们就是在调用这个构造方法、我们也可以根据自己的需求、定义自己的构造方法。
注意1: 当有了自己的构造方法时、默认的构造方法将会失效、不能使用
-- 当然、你可能希望明确的写一个默认的构造方法、但是会和我们自定义的构造方法冲突
-- 这是因为Dart语言本身不支持函数的重载(名称相同、参数不同的方式)注意2: 这里我还实现了toString方法。
class Person { String name; int age; // 明确指定了构造方法、如果没有指定默认是Person() Person(String name, int age) { this.name = name; this.age = age; } @override String toString() { return 'name = ${name}, age = ${age}'; } }
另外、在实现构造方法时、通常做的事情就是通过 **参数给属性 **赋值,如上this.name = name
为了简化了这一过程、Dart提供了一种更加简洁的语法糖形式
上面的构造方法可以优化成下面的写法:// 明确指定了构造方法、如果没有指定默认是Person() Person(String name, int age) { this.name = name; this.age = age; } // 等同于上面优化构造方法 Person(this.name, this.age);
9.2.2: 命名构造方法
但是在开发中、我们确实希望实现更多的构造方法、怎么办呢?
- 因为不支持方法(函数)的重载、所以我们没办法创建相同名称的构造方法。我们需要使用命名构造方法。
class People { String name; int age; People() { name = ''; age = 0; } // 命名构造方法, withArgments可以随便变化 People.withArgments(String name, int age) { this.name = name; this.age = age; } // 优化上面的构造方法 //People.withArgments(this.name, this.age); @override String toString() { return 'name=${name} age=${age}'; } } /* * name= age=0 name=lishengbing age=28 */ main(List<String> args) { var p = new People(); print(p); var p1 = new People.withArgments('lishengbing', 28); print(p1); }
在之后的开发中、我们也可以利用命名构造方法、提供更加便捷的创建对象的方式
- 比如开发中、我们需要经常将一个Map转成对象、可以提供如下的构造方法
// 新的构造方法 People.fromMap(Map<String, Object> map) { this.name = map['name']; this.age = map['age']; } var p3 = new People.fromMap({'name': 'lishengbing', 'age': 29}); // name=lishengbing age=29 print(p3);
9.2.3: 初始化列表
我们来重新定义一个类Point、传入x/y、可以得到它的距离distance:
初始化列表 class Point { final num x; final num y; final distance; // 错误写法 Point(this.x, this.y) { distance = sqrt(x * x + y * y); }*/ // 正确写法 Point(this.x, this.y) : distance = sqrt(x * x + y * y); }
上面这种初始化变量的方法、我们称之为初始化列表(Initializer list)
9.2.4: 重定向构造方法
在某些情况下、我们希望在一个构造方法中去调用另一个构造方法、这时候我们就可以使用重定向构造方法。
- 在一个构造函数中、去调用另一个构造函数(注意:是在冒号后面使用this调用)
class Person01 { String name; int age; Person01(this.name, this.age); Person01.fromName(String name) : this('lishengbing', 30); }
9.2.5: 常量构造方法
在某些时候下、传入相同值时、我们希望返回同一个对象、这个时候、可以使用常量构造方法
默认情况下、创建对象时、即使传入相同的参数、创建出来的对象也不是同一个对象,看下面的代码:
- 我们使用identical(对象1, 对象2)函数来判断两个对象是否是同一个对象
class Person02 { String name; Person02(this.name); } main(List<String> args) { var p1 = Person02('lishengbing'); var p2 = Person02('lishengbing'); // false print(identical(p1, p2)); }
- 但是如果将构造方法前加上 const进行修饰的话、那么可以保证同一个参数、创建出来的对象是相同的。
这样的构造函数就叫做常量构造方法class Person02 { final String name; const Person02(this.name); } main(List<String> args) { var p1 = const Person02('lishengbing'); var p2 = const Person02('lishengbing'); // true print(identical(p1, p2)); }
常量构造方法有一些注意点:
- 注意一:拥有常量构造方法的类中、所有的成员变量必须是final学校修饰的、如final String name;
- 注意二:为了可以使用常量构造方法、创建出相同的对象,不再使用new 关键字、而是使用const关键字
-- 如果是将结果赋值给const修饰的标识符时、const可以省略。9.2.5: 工厂构造方法
Dart 提供了factory关键字、用于通过工厂去获取对象。
工厂获取得到的对象传入同一个参数、得到的是同一个对象
传入不同的参数、获取的对象不是同一个对象。main(List<String> args) { var p1 = Person03('object'); var p2 = Person03('object'); // 工厂构造方法获取对象比较=true print('工厂构造方法获取对象比较=${identical(p1, p2)}'); } class Person03 { String name; static final Map<String, Person03> _cache = <String, Person03>{}; factory Person03(String name) { if (_cache.containsKey(name)) { return _cache[name]; }else { final p = Person03._internal(name); _cache[name] = p; return p; } } Person03._internal(this.name); }
9.2.6: setting & getting
默认情况下、Dart中类定义的属性是可以被外界直接访问的
但是某些情况下、我们希望监控这个类的属性被访问的过程、这个时候就可以使用setting 和 getting了main(List<String> args) { final d = Dog('黄色'); //打印就是: dog color = 黑色 d.setColor = '黑色'; //打印就是: dog color = 红色 d.color = '红色'; print('dog color = ${d.getColor}'); } class Dog { String color; String get getColor { return color; } set setColor(String color) { this.color = color; } Dog(this.color); }
9.2.7: 类的继承(仅支持单继承、多继承不可以)
1: 面向对象的其中一大特性就是继承、继承不仅可以减少我们的代码量、也是多态的使用前提
2: dart 中的继承使用extends 关键字、子类中使用super来访问父类;
3: 父类中的所有成员变量和方法都会被继承、但是构造方法除外.
main(List<String> args) { var p = new Person04(); p.age = 10; p.run(); /// 奔跑ing /// 继承=10 print('继承=${p.age}'); } class Animal { int age; run() { print('奔跑ing'); } } class Person04 extends Animal { }
9.2.7(1): 子类可以拥有自己的成员变量、并且可以对父类的方法进行重写
class Person04 extends Animal { @override run() { // TODO: implement run 1: 如果子类重写了父类的方法,打印父类的方法 //我是Person04类的重写run方法 //继承=10 print('我是Person04类的重写run方法'); } }
9.2.7(2): 子类中可以调用父类的构造方法、对某些属性进行初始化
- 子类的构造方法在执行前, 将隐式调用父类的默认的无参数的构造方法(没有参数且类同名的构造方法)
- 如果父类没有无参默认构造方法、则子类的构造方法必须在初始化列表中通过super显式调用父类的某个构造方法。
class Animal { int age; run() { print('奔跑ing'); } // 只要这样写、该类就没有了无参默认构造函数 Animal(this.age); } class Person04 extends Animal { String name; Person04(String name, int age) : name=name, super(age); @override run() { // TODO: implement run // 1: 如果子类重写了父类的方法,打印父类的方法 // 我是Person04类的重写run方法 // 继承=10 print('我是Person04类的重写run方法'); } @override String toString() { print('toString-'); } }
9.2.8: 抽象类
我们知道、继承是多态使用的前提
所以在定义很多通用的通用接口时我们通常会让调用者传入父类、通过多态类实现更加灵活的调用方式。但是、父类本身可能并不需要对某些方法进行具体的实现、所以父类中定义的方法、我们可以定义为抽象类。
什么是抽象方法?在Dart中没有具体实现的方法(没有方法体)、就是抽象方法。
- 抽象方法、必须存在于抽象类中。
- 抽象类是使用abstract 声明的类。
下面这个Shape类就是一个抽象类、其中包含了一个抽象方法
abstract class Shape {
getArea();
}
class Circle extends Shape {
double r;
Circle(this.r);
@override
getArea() {
// TODO: implement getArea
return r * r * 3.14;
}
}
class Reactangle extends Shape {
double w;
double h;
Reactangle(this.w, this.h);
@override
getArea() {
// TODO: implement getArea
return w * h;
}
}
注意事项:
- 注意一:** 抽象类中不能实例化;
- 注意二:** 抽象类中的抽象方法必须被子类实现、抽象类中的已经被实现方法、可以不被子类重写;
9.2.9: 隐式接口
Dart中的接口比较特殊、没有一个专门的关键字来声明接口
默认情况下、定义的每一个类都相当于默认也声明了一个接口、可以由其他的类来实现(因为Dart不支持多继承)在开发中、我们通常将用于给别人来实现的类声明为抽象类:
abstract class Runner {
run();
}
abstract class Flyer {
fly();
}
class SuperMan implements Runner, Flyer {
@override
run() {
// TODO: implement run
print('超人在跑');
}
@override
fly() {
// TODO: implement fly
print('超人在飞');
}
}
9.2.10: Mixin混入
在通过implements实现某个类时、类中所有的方法都必须被重载实现(无论这个类原来是否已经实现过该方法)
但是在某些情况下、一个类可能希望直接复用之前类的原有实现方案、怎么做呢?
- 使用继承吗?但是Dart只支持单继承、那么意味着你只能复用一个类的实现
Dart提供了另外一种方案:Mixin混入的方式
- 除了可以class 定义类之外、也可以通过mixin关键字来定义一个类
- 只是通过mixin定义的类用于被其他类混入使用、通过with关键字来进行混入。
main(List<String> args) {
var superMan = SuperMain();
superMan.run();
superMan.fly();
/*
在奔跑...
在飞翔...
*/
}
mixin Runner1 {
run() {
print('在奔跑...');
}
}
mixin Flyer1 {
fly() {
print('在飞翔...');
}
}
/// implements的方式必须要求对其中的方法进行重新实现
class SuperMain0 implements Runner1, Flyer1 {
@override
run() {
// TODO: implement run
return null;
}
@override
fly() {
// TODO: implement fly
return null;
}
}
class SuperMain with Runner1, Flyer1 {
}
9.2.11: 类成员和方法
前面我们在类中定义的成员和方法都属于对象级别的、在开发中、我们有时候也需要定义类级别的成员和方法
在Dart中我们使用static关键字类定义:
main(List<String> args) {
var stu = Student();
stu.name = 'lishengbing';
stu.age = 66;
stu.study();
Student.time = '早上10点';
Student.attendClass();
// lishengbing在学习
// 去上课
}
class Student {
String name;
int age;
static String time;
study() {
print('$name在学习');
}
static attendClass() {
print('去上课');
}
}
9.2.12: 枚举的类型
枚举在开发中也非常常见、枚举也是一种特殊的类、通常用于表示数量的常量值。
1: 枚举的定义
枚举使用enum关键字来进行定义
// 1: 枚举的定义
main(List<String> args) {
// Colors.red
print(Colors.red);
// values, [Colors.red, Colors.green, Colors.blue]
print(Colors.values);
// index, 0
print(Colors.red.index);
}
enum Colors {
red,
green,
blue
}
2: 枚举的属性
枚举类型中有两个比较常见的属性
- index: 用于表示每个枚举常量的索引、从0开始
如:print(Colors.red.index);- values:包含每个枚举值的List
如:print(Colors.values);3: 枚举的注意事项
- 注意1: 您不能子类化、混合或者实现枚举。
- 注意2: 不能显式实例化一个枚举。
9.3: 泛型
1: 为什么使用范型?
......
2: List和Map的范型
main(List<String> args) {
// 1: 创建List的方式
var name1 = ['lishengbing', 'zhangsan', 'wangxiao', 111];
// List<String>
// List<Object>
print(name1.runtimeType);
// 限制类型
var name2 = <String>['1', '2', '3'];
// List<String>
print(name2.runtimeType);
// 2: Map使用泛型的写法
var info1 = {'name': 'li', 1: 'one', 'age': 20};
// _InternalLinkedHashMap<Object, Object>
print(info1.runtimeType);
// 2: 对类型进行限制
Map<String, String> info2 = {'name': 'li', 'age': '20'};
// 限制写法1
// info2=_InternalLinkedHashMap<String, String>
print('info2=${info2.runtimeType}');
var info3 = <String, String>{'name': 'li', 'age': '20'};
// 限制写法2
// info3=_InternalLinkedHashMap<String, String>
print('info3=${info3.runtimeType}');
}
3: 类定义的泛型
如果我们需要定义一个类、用于存储位置信息Location、但是并不确定使用者使用的是int类型、还是double类型、甚至是一个字符串类型、这个时候该怎么定义呢?
- 一种方案是使用Object类型、但是在之后使用时候非常不方便
- 另一种方案就是使用泛型
Location类的定义:Object类 || 泛型方式
main13(List<String> args) {
Location l = Location(10, 20);
/// int
print(l.x.runtimeType);
}
class Location {
Object x;
Object y;
Location(this.x, this.y);
}
main(List<String> args) {
Location2 l1 = Location2<int>(10, 30);
// Location2<int>
print('int-l1=${l1.runtimeType}');
Location2 l2 = Location2<String>('30', '40');
// Location2<String>
print('String-l2=${l2.runtimeType}');
}
// 泛型写法
class Location2<T> {
T x;
T y;
Location2(this.x, this.y);
}
- 如果我们希望类型只能是num类型、怎么做呢?
// 如果希望类型只能是num类型
main(List<String> args) {
Location3 l3 = Location3(10, 20);
// Location3<num>
print(l3.runtimeType);
}
class Location3<T extends num> {
T x;
T y;
Location3(this.x, this.y);
}
4: 泛型方法的定义
最初、Dart仅仅在类中支持泛型、后来一种称之为泛型方法的新语法允许在方法和函数中使用类型参数。
main(List<String> args) {
var names = ['1', '2'];
var first = getFirst(names);
/// first = 1, type=String
print('first = ${first}, type=${first.runtimeType}');
}
T getFirst<T>(List<T> ts) {
return ts[0];
}