介绍
本片文章有点长,主要讲了 Dart 中 类、泛型和库 几个重要的概念。请耐心看下去。并没有给出过多的解释,我觉得大家通过实例慢慢去消化,自己去理解,才是对大家最大的帮助。如有不合适,评论留言,下次我会给每一段代码更多的解释
Class 类
类(Class)是面向对象程序设计,实现信息封装的基础。类是一种用户定义的类型。每个类包含数据说明和一组操作数据或传递消息的函数。类的实例称为对象。
Dart是一门使用类和单继承的面向对象语言,具有类和基于mixin的继承。所有的对象都是一个类的实例,所有的类都继承自Object。基于mixin的继承意味着尽管任何一个类(除了Object)都只有一个父类,但是类主体可以在多个类层次结构中复用。
Dart的类与其它语言都有很大的区别,比如在dart的类中可以有无数个构造函数,可以重写类中的操作符,有默认的构造函数,由于dart没有接口,所以dart的类也是接口,因此你可以将类作为接口来重新实现
构造函数
使用new语句来构造一个类,构造函数的名字可能是 ClassName
,也可以是 ClassName.otherName
var example = new Example(1, 2); //new 可省略 var example = Example(1, 2);
print(example); // Example(1, 2)
// 这种写法是不是很像 JAVA 啊
class Example {
int x;
int y;
Example(int x, int y) {
this.x = x;
this.y = y;
}
}
// 但在 Dart 中是可以简化成这样的 (推荐)
class Example {
int x;
int y;
Example(this.x, this.y);
}
命名构造函数
使用命名构造函数可以为一个类实现多个构造函数, 或者使用命名构造函数来更清晰的表明你的意图
var example = Example.fromJson({'x': 2, 'y': 2});
class Example {
int x;
int y;
Example(this.x, this.y);
// 命名构造函数
Example.fromJson(Map json) {
x = json['x'];
y = json['y'];
}
}
重定向构造函数
有时候构造函数的目的只是重定向到该类的另一个构造函数。一个重定向构造函数是没有代码的,在构造函数声明后, 使用冒号调用其他构造函数
var example = Example.alongXAxis(0);
print(example);
class Example {
int x;
int y;
Example(this.x, this.y);
// 重定向构造函数,使用冒号调用其他构造函数
Example.alongXAxis(int x) : this(x, 0);
}
初始化列表
在构造函数体执行之前可以初始化实例参数。 使用逗号分隔初始化表达式。初始化列表非常适合用来设置 final 变量的值
class Example {
final String name;
final int age;
final String description;
// 初始化列表
Example(x, y) :
x = x,
y = y,
description = description;
}
调用超类构造函数
- 超类命名构造函数不会传递,如果希望使用超类中定义的命名构造函数创建子类,则必须在子类中实现该构造函数
- 如果超类没有默认构造函数,则你需要手动的调用超类的其他构造函数
- 调用超类构造函数的参数无法访问 this
- 在构造函数的初始化列表中使用 super(), 需要把它放到最好
class Parent {
int x;
int y;
// 父类命名构造函数不会传递
Parent.fromJson(x, y)
: x = x,
y = y {
print('父类命名构造函数');
}
}
class Child extends Parent {
int x;
int y;
// 若超类没有默认构造函数,需要手动调用超类其他构造函数
Child(x, y) : super.fromJson(x, y) {
// 调用父类构造函数的参数无法访问 this
print('子类构造函数');
}
// 在构造函数的初始化列表中使用 super(), 需要把它放到最好
Child.fromJson(x, y)
: x = x,
y = y,
super.fromJson(x, y) {
print('子类命名构造函数');
}
}
常量构造函数
注意事项:
-
定义 const 构造函数要确保所有实例变量都是 **final **
-
const 关键字放在构造函数名称之前
class Example {
// 定义 const 构造函数要确保所有实例变量都是 final
final int x;
final int y;
static final Example origin = const Example(0, 0);
// const 关键字放在构造函数名称之前,且不能有函数体
const Example(this.x ,this.y);
}
工厂构造函数
- 工厂构造函数是一种构造函数,与普通构造函数,工厂函数不会自动生成实例,而是通过代码来决定返回的实例对象
- 如果一个构造函数并不总是返回一个新的对象,则使用 factory 来定义这个构造函数
- 工厂构造函数无法访问 this
class Singleton {
String name;
// 工厂构造函数无法访问this,所以这里要用 static (这个变量是属于类的,而不是属于对象的)
static Singleton _cache;
// 工厂方法构造函数,关键字 * factory *
factory Singleton([String name = 'singleton']) => Singleton._cache ??= Singleton._newObject(name);
// 定义一个命名构造函数来生产实例
Singleton._newObject(this.name);
}
=========================================================================================
// 工厂函数
class Massage {
void doMassage(){
print('按摩');
}
}
class FootMassage extends Massage {
@override
doMassage() {
print('脚底按摩');
}
}
class BodyMassage extends Massage {
@override
void doMassage() {
print('全身按摩');
}
}
class SpecialMassage extends Massage {
@override
void doMassage() {
print('特殊按摩');
}
}
Massage massageFactory(String type){
switch (type) {
case 'foot':
return new FootMassage();
case 'body':
return new BodyMassage();
default:
return new SpecialMassage();
}
}
Setter 和 Getter
- 每个实例变量都隐含的具有一个 getter, 如果变量不是 final 的则还有一个 setter
- 可以通过实行 getter 和 setter 来创建新的属性,使用
get
和set
关键字定义 getter 和 setter - getter 和 setter 的好处是,你可以开使用实例变量,可以把实例变量用函数包裹起来,而调用你代码的地方不需要修改
class Rectangle {
int left;
int top;
int width;
int height;
Rectangle(this.left, this.top, this.width, this.height);
int get right => left + width; // 获取right值
set right(int value) => left = value - width; //设 置right值,同时left也发生变化
int get bottom => top + height;
set bottom(int value) => top = value - height;
}
抽象类
Dart 抽象类主要用于定义标准,子类可以继承抽象类,也可以实现抽象类接口,抽象类通过 abstract
关键字来定义
- 抽象类不能被实例化,除非定义一个工厂构造函数或者继承它的子类可以
- 抽象类通常用来定义接口,以及部分实现
- 抽象类通常具有抽象方法,抽象方法不需要关键字,以分号结束即可
- 抽象方式使用时,需要重写抽象类的成员变量和方法,包括私有的
- 一个类可以
implement
一个普通类。Dart 任何一个类都是接口 - 一个类可以
implement
多个接口
extends抽象类 和 implements的区别:
- 如果要复用抽象类里面的方法,并且要用抽象方法约束子类的话我们就用extends继承抽象类
- 如果只是把抽象类当做标准的话我们就用 implements实现抽象类
经典案例:
// 定义一个抽象类,并定义两个抽象方法
abstract class Animal {
eat();
run();
printInfo() {
print('我是一个抽象类里面的普通方法');
}
}
// 定义一个 Dog 继承 Animal 并重写其中的方法
class Dog extends Animal {
@override
eat() {
print('小狗在吃骨头');
}
@override
run() {
print('小狗在跑');
}
}
class Cat extends Animal {
@override
eat() {
print('小猫在吃老鼠');
}
@override
run() {
print('小猫在跑');
}
}
main() {
Dog dog = Dog(); // 实例化 Dog
dog.eat(); // 小狗在吃骨头
dog.run(); // 小狗在跑
dog.printInfo(); // 我是一个抽象类里面的普通方法
Cat cat = Cat();
cat.eat(); // 小猫在吃老鼠
cat.run(); // 小猫在跑
cat.printInfo(); // 我是一个抽象类里面的普通方法
}
不知道大家知不知道 @override 是个啥,可以当做是重写吧
可调用类
实现 call() 方法可以让类像函数一样能够被调用
class TransferClass {
call(String a, String b, String c) => '$a $b $c!';
}
main() {
var transfer = TransferClass();
var String = transfer("dart","flutter","top");
print('$test'); // dart flutter top!
print(transfer.runtimeType); // TransferClass
print(test.runtimeType); // String
print(transfer is Function); // false
}
Mixin 泛型
使用泛型,很多的容器对象,在创建对象时都可以定义泛型类型,跟 Java 一样
var list = List<String>();
var map = Map<int, String>();
// 运行时可判断泛型
print(list is List<String>); // true
print(list.runtimeType); // JSArray<String>
泛型函数
Dart1.21 开始可以使用泛型函数
泛型函数可以在以下几个地方使用类型参数:
<1> 函数的返回值类型
<2> 参数的类型
<3> 局部变量的类型
main() {
K addCache<K, V>(K key, V value) {
K temp = key;
print('${key} : ${value}'); // dart : flutter
return temp;
}
var key = addCache('dart', 'flutter');
print(key); // dart
}
泛型构造函数
要在使用构造函数时指定一个或多个类型,可将类型放在类名称后面的尖括号<...>中
main() {
var phone = Phone<String>('6888');
print(phone.mobileNumber); // 6888
}
class Phone<T> {
final T mobileNumber;
Phone(this.mobileNumber);
}
泛型限制
实现泛型类型时,您可能希望限制其参数的类型,可以在<>里面使用extends
main() {
var footMessage = FootMessage();
var message = Message<FootMessage>(footMessage);
message.message.doMessage(); // 脚底按摩
}
class Message<T extends FootMessage> {
final T message;
Message(this.message);
}
class FootMessage {
void doMessage() {
print('脚底按摩');
}
}
库
使用 import
去指定如何在另一个库的范围内使用来自一个库的命名空间
import 后的参数必须参数为库的 URL (Uniform Resource Identifier统一资源标识符)
例如,Dart web应用程序通常使用 Dart : html 库,它们可以像这样导入:
import 'dart:html';
使用核心库
对于内置的库,URI 使用特殊的 dart: scheme
import 'dart:math';
import 'dart:io';
import 'dart:convert';
....
void main() {
print(sqrt(4)); // math > 开平方2.0
}
载入第三方库
对于其他的库,你可以使用文件系统路径或者 package: scheme
如果需要载入第三方库我们是需要在 pubspec.yaml 文件中声明需要引用的库,使用 Packages get 进行拉取
编写 pubspec.yaml:
dependencies:
flutter:
sdk: flutter
cupertion_icons: ^0.1.0
dio: ^2.1.0
调用:
import 'package:dio/dio.dart'; // Dio 一个很强大的网络请求库
void main() {
getHttp();
}
void getHttp() async {
try {
Response response = await Dio().get("http://www.baidu.com");
} catch (e) {
print(e);
}
}
在上方的例子中我们使用了 async 和 await ,大家可以自行预习,下篇文章应该会讲到
载入文件
我们先创建一个 mylib.dart
的文件,并写入以下内容,写啥都不要紧,随便写
class MyLib {
String name;
static MyLib _cache;
factory MyLib([String name = 'singleton']) => MyLib._catch ??= MyLib._newObject(name);
MyLib._newObject(this.name);
}
我们在另一个文件中引入 mylib.dart 文件, 我这里是同一级目录,所以直接这么引入哈
import 'mylib.dart';
void main() {
var myLib = MyLib(); // 实例化 mylib.dart 中的类
}
指定库前缀
如果两个库有冲突的标识符,可以为其中一个或两个库都指定前缀来避免冲突
假设我们有 mylib1.dart
和 mylib2.dart
这两个文件都有一个名为 MyLib 的类
class MyLib() {
String name;
MyLib(this.name);
}
这个时候我们需要在一个地方同时引入这两个文件,并实例化 MyLib
import 'mylib1.dart';
import 'mylib2.dart';
void main() {
var myLib1 = MyLib();
var myLib2 = MyLib();
}
不用我说了,大家看着都有问题,那我们是不是可以指定一个前缀(别名) 呢, 在dart里可以使用 as
来为引入的文件指定一个前缀
import 'mylib1.dart' as lib1;
import 'mylib2.dart' as lib2;
void main() {
var myLib1 = lib1.MyLib();
var myLib2 = lib2.MyLib();
}
选择性载入
如果你只需要使用库的一部分功能,则可以选择需要导入的内容
- show 只载入库的某些部分
- hide 筛选掉库的某些部分
import 'mylib1.dart' as lib1 show Test
import 'mylib2.dart' as lib2 hide Test
延迟载入
- 使用 await 关键字暂停代码执行一直到库加载完成
- 可提高程序的启动速度
- 用在不常使用的功能
- 用在载入时间过长的包
- 执行 A/B 测试,例如尝试各种算法的不同实现
使用 deferred as
导入,使用标识符调用 loadLibrary() 加载库
import 'mylib1.dart' deferred as lazyLib;
void main() {
lazyLoad()''
}
lazyLoad () async {
await lazyLib.loadLibrary();
var t = lazyLib.Test();
t.test();
}
自定义库
用 library 来来命名库,用part来指定库中的其他文
-
part 可以把一个库分开到多个 Dart 文件中
-
或者我们想让某一些库共享它们的私有对象的时候,可以需要使用 part
-
import 不会完全共享作用域,而 part 之间是完全共享的。如果说在A库中import了B库,B库import了C库,A库是没有办法直接使用C库的对象的。而B,C若是A的part,那么三者共享所有对象。并且包含所有导入
注意:不必在应用程序中(具有顶级main()函数的文件)使用library,但这样做可以让你在多个文件中执行应用程序
// mylib/tool.dart
part of mylib;
void printTool() => print('tool')
// mylib/util.dart
part of mylib;
void printUtil() => print('util');
// mylib/mylib.dart
library mylib;
part 'util.dart';
part 'tool.dart';
void printMyLib() => print('mylib');
import 'mylib/mylib.dart';
void main() {
printMyLib();
printUtil();
printTool();
}