该部分内容主要分为四部分, 认真看, 一小时, 带你学完Dart基础语法:
测试工具我选择的是VSCode, 打开空白文件夹开始Dart基础语法的学习吧...
设计个满足需求的函数
- 需求是,
Person有两个属性,名字和肤色. - 要求初始化时, 传入
相同的名字, 返回的是同一对象. - 要求初始化时, 传入
相同的肤色, 返回的是同一对象.
方案一: 使用常量构造函数
一起构造两个属性
这种情况必须当名字和颜色完全相同时, 才能够保证书输出统一对象.
class Person1 {
final String name;
final String color;
const Person1(this.name, this.color);
}
void main(List<String> arguments) {
const p1 = Person1('lilei', '红');
const p2 = Person1('lilei', '红');
const p3 = Person1('lilei', '蓝');
print(identical(p1, p2)); // true
print(identical(p2, p3)); // false
}
显然, Person1 并不能够满足需求, 所以要单独构造两个属性, 但不能够满足条件:
class Person2 {
final String name;
final String color = "white";
const Person2(this.name);
// ❌错误: The unnamed constructor is already defined.
// 常量构造函数, 不能够有多个构造函数, 所以不能满足输入同一颜色返回同一对象的操作
// 在 Dart语法精炼(二)中第10 知识点有讲解.
const Person2(this.color);
}
方案二: 使用工厂构造函数
11. 工厂构造函数
11.1 普通构造函数 的返回值
- 普通的构造函数: 会
自动返回创建出来的对象, 不能手动返回
class Person3 {
String name;
Person3(this.name) {
// return this; // 系统默认帮助实现了
}
}
11.2 工厂构造函数
- 工厂构造函数最大的特点: 可以
手动的, 且必须返回一个对象, - 看似多占用内存, 但是减少了对象创建和销毁的过程,
- 如果外面初始化对象过多, 不使用工厂模式, 也是多占内存, 反过来看工厂模式整体来看还是为了节省内存的.
- 使用
factory关键字, 修饰命名构造函数的形式来完成工厂模式的创建.(factory Person.formMap(){}) - 注: 一旦自定义了类的初始化方法, 系统就不会生成默认的初始化方法了.
class Person4 {
String name;
String color;
// 使用关键字 static 可以声明 类变量 或 类方法。
static final Map<String, Person4> _nameCache = {};
static final Map<String, Person4> _colorCache = {};
factory Person4.withName(String name) {
if (_nameCache.containsKey(name)) {
return _nameCache[name]!;
} else {
final p = Person4(name, "default");
_nameCache[name] = p;
return p;
}
}
factory Person4.withColor(String color) {
if (_colorCache.containsKey(color)) {
return _colorCache[color]!;
} else {
final p = Person4("default", color);
_colorCache[color] = p;
return p;
}
}
// 自定义初始化构造函数, 因为当自定义了withName/withColor 后, 系统就会默认不生成Person4()的构造方法, 这里需要自定义初始化构造方法
Person4(this.name, this.color);
}
在使用上, 满足只要名称或者颜色相同, 就能得到同一对象:
final p3 = Person4.withName("zhangsan");
final p4 = Person4.withName("zhangsan");
print(identical(p3, p4)); // true
final p5 = Person4.withColor("red");
final p6 = Person4.withColor("red");
print(identical(p5, p6)); // true
11.3 通常定义属性选择用 final
- p3, p4, p5, p6 也可以用
var修饰, 这里使用final, 就是一种语法习惯 - 类似于Swift的
let/var, 一般都用let, 这里也是一样, 用final修饰, 一下就知道后续不可变了, 比较安全.
12. 类的getter和setter
- 实例对象的每一个属性都有一个隐式的 Getter 方法,如果为非
final属性的话还会有一个 Setter 方法, - 一般在使用上直接访问属性即可:
void main(List<String> arguments) {
final p = Person();
// 直接访问属性 set
p.name = 'lilei';
// 直接访问属性 get
print(p.name); // lilei
print(p.height); // 1.88
}
class Person {
String? name;
double height = 1.88;
}
- 可以使用
get和set关键字自定义属性的 Getter 和 Setter 方法:
void main(List<String> args) {
final p = Person2();
// 通过setter/getter 访问
p.setName = "lilei";
print(p.getName); // lilei
p.setHeight = 2.88;
print(p.getHeight); // 2.88
}
class Person2 {
String? name;
double height = 1.88;
// setter 语法糖
set setName(String name) {
this.name = name;
}
// getter 语法糖
String get getName {
return this.name ?? "default";
}
// setter/getter 也可以这么写
double get getHeight => this.height;
set setHeight(double height) => this.height = height;
}
- 也可以使用
get和set关键字为额外的属性添加 Getter 和 Setter 方法:
void main(List<String> arguments) {
final r = Rectangle(15, 5, 30, 50);
r.right = 70;
print(r.left); // 40.0
print(r.bottom); // 55.0
}
// 矩形 自己定义了left, top, width, height 四个属性,
// 还有两个 额外的 计算型属性 right 、bottom
class Rectangle {
double left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
// Define two calculated properties: right and bottom.
double get right => left + width;
set right(double value) => left = value - width;
double get bottom => top + height;
set bottom(double value) => top = value - height;
}
13. 类的继承
13.1 extends 关键字就表示继承自谁
- 定义动物类型父类:
class Animal {
// 年龄
int age;
// 初始化方法
Animal(this.age);
// 吃 方法
void eat() {}
}
- 定义具体动物'狗'继承自动物类:
// extends 就表示继承自谁
class Dog extends Animal {
// 定义属于自己的属性 - 狗狗名称
String name;
// 为super传入age 完成初始化
// : super(age) 就是在初始化列表里面调用的super
Dog(this.name, int age) : super(age);
// @override 重写一个方法, 需要使用@override关键词
@override
void eat() {
// TODO: implement eat
super.eat();
}
}
使用上:
void main(List<String> arguments) {
final animla = Animal(20);
print(animla.age); // 20
final dog = Dog('wangcai', 3);
print('${dog.name} ${dog.age}'); // wangcai 3
}
13.2 extends 在泛型里面还被用作类型限制
- 一种常见的非空类型处理方式,
- 将子类限制继承
Object(而不是默认的 Object?)
class Foo<T extends Object> {
// provided: 提供, 供给
// Any type provided to Foo for T must be non-nullable.
}
限制泛型参数类型eg: 当实现一个泛型时,如果需要限制它参数的类型,可以使用extends 关键字
void main(List<String> arguments) {
final math = Subject();
final xiaoming = Learn(math);
xiaoming.math.score(); // 100分
}
class Subject {
void score() {
print("100分");
}
}
// 泛型限制
class Learn<T extends Subject> {
final T math;
Learn(this.math);
}
13.2 浅谈一下泛型
14. 抽象类的使用
14.1 抽象类的定义
- 抽象类 用
abstract关键字修饰, - 可以只声明方法, 不实现方法, 但是子类必须实现, 这个方法叫做
抽象方法, - 也可以声明方法, 并且实现它, 子类就可以不实现.
eg: 定义一个 形状 抽象类
abstract class Shape {
// 1.1 可以只声明方法, 不实现方法, 但是子类必须实现
double getArea();
// 1.2 也可以声明方法, 并且实现它, 子类可以不实现
String getInfo() {
return "我是形状抽象类";
}
}
// 注意一: 继承自抽象类后, 必须实现抽象类的抽象方法
class Rectangle extends Shape {
@override
double getArea() {
return 100.00;
}
}
- 抽象类不能被实例化,只有继承它的子类可以.
14.2 验证抽象类不能被实例化
void main(List<String> arguments) {
// 直接实例化形状抽象类
// ❌报错: Abstract classes can't be instantiated.
final shape = Shape();
}
Map、List都是抽象类, 但是都能直接实例化, 为什么???
bstract class List<E>
abstract class Map<K, V>
实例化:
final map = Map(); // 不报错
print(map.runtimeType); // _Map<dynamic, dynamic>
final list = []; // 不报错
print(list.runtimeType); // List<dynamic>
在 Dart 中,Map 是一个接口(interface),而不是一个抽象类。接口是一种特殊的抽象类,它只包含了抽象方法的定义,而没有具体的实现。和抽象类一样,接口也不能被直接实例化。
在 Dart 中,我们使用 Map 接口的实现类来创建实例。Dart 中内置了 Map 接口的多种实现类,如 LinkedHashMap、HashMap 等。我们可以通过调用这些实现类的构造函数来创建实例。
并且Map的初始化采用了工厂模式(factory), 且被external 关键字修饰, 而这, 也就是抽象类可以被实例化的原因.
在 Dart 中,external factory 关键字组合用于声明一个外部工厂构造函数,它表示这个构造函数是由其他外部资源(比如 JavaScript 代码、本地库等)提供的,而不是由 Dart 代码实现的。这个关键字组合的作用是告诉 Dart 编译器不要生成这个构造函数的实现代码,而是将其链接到其他的外部资源。
14.3 external 关键字
通过分析Map的实例化源码来了解external 关键字.
- Map的工厂函数声明
external标识符: 代表我这里只做一个实例化方法(Map())的声明, 具体实现是由其他外部资源完成的.external作用: 将方法的实现和方法的声明分离.
external factory Map();
- Map的工厂函数实现
- 在 Dart 2.0之前,
external修饰的函数实现, 是通过补丁, 将文件放在Dart的SDK中的, 通过dart 中Map 的源码文件, 路径为:flutter/bin/cache/dart-sdk/lib/_internal/vm/lib/map_patch.dart
@patch
factory Map() => new LinkedHashMap<K, V>();
-
通过@patch 修饰后, 来继续实现实例化方法(Map()), 这么做的好处就是可以在runtime过程中, 动态获取运行平台, 使用不同的实现方式来实现.
-
@patch作用: 关联某个类中用external修饰的方法的实现. -
在 Dart 2.0之后,
external factory Map()的具体代码实现是由 Dart 运行时系统提供的,而不是由 Dart 代码实现的。所以就不需要我们来关心实现了.
14.3 抽象类的实例化
- 采用工厂(factory)构造函数, 返回一个子类的实例化对象,
- 一旦在抽象类中定义了工厂构造方法后,子类将不能通过继承(extends), 只能通过接口(implements)来完成实现,
- 一旦在抽象类中定义了工厂构造方法后,子类中将 不能定义除工厂构造方法外的其它构造方法了,
- 如果把抽象类当做 接口 实现的话 必须得实现抽象类里面定义的所有属性和方法。
void main(List<String> arguments) {
final shape = Shape();
print(shape.runtimeType); // triangle
print(shape.getArea()); // 18.8
print(shape.getInfo()); // 我是三角形
}
abstract class Shape {
double getArea();
String getInfo() {
return "我是形状抽象类";
}
// 开启了工厂模式
factory Shape() {
return Triangle();
}
}
// 抽象类开启了工厂模式, 子类只能通过接口(implements)实现
class Triangle implements Shape {
@override
double getArea() {
return 18.8;
}
@override
String getInfo() {
return "我是三角形";
}
}
15. 隐式接口
- Dart中没有关键字来定义接口的
- 其他语言比如用
interface/protocol来定义接口 - Dart 默认情况下所有的类都是隐式接口
- Dart 只支持单继承, 所以
Person想会跑, 又想会飞, 就不能通过继承来实现
class Runner {
void running() {
print("running");
}
}
class Flyer {
void flying() {
print("flying");
}
}
class Person {
void eating() {}
}
Person 无法既继承Runner, 又继承Flyer.
15.1 接口的使用 (implements)
- 当一个类当做接口使用时, 那么实现这个接口的类,
必须实现这个接口中的所有方法 - 当Runner 和 Flyer 当做接口时, 实现接口的类就是 SuperMan, 标识就是
implements关键字, 所以SuperMan 要实现Runner 和Flyer 的所有方法 - 只可以单继承
extends Person;可以多接口, 用逗号隔开 - 继承(extends)才可以调用
super, 比如eating; 接口(implements)不可以, 比如running , flying. - 当Person 也存在running 的时候, SuperMan里面可以不实现, 因为继承自person, 就证明已经有了, 不需要再实现一次了.
class SuperMan extends Person implements Runner, Flyer {
@override
void eating() {
// TODO: implement eating
super.eating();
}
@override
void running() {
// TODO: implement running
}
@override
void flying() {
// TODO: implement flying
}
}
结语
路漫漫其修远兮,吾将上下而求索~
.End