Dart 是面向对象编程语言,对象都是由类创建的,所有类都是由 Object 类派生出来的子类,除了 Object , 所有类只有一个父类(即只能继承一个父类)
尽管 Dart 语言中一个类只能继承一个父类,但是 Dart 语言提供了 mixin 机制,可以复用多个类,达到类似多继承的效果
类和对象
1)、Dart 没有 public、protected 和 private 等成员访问限定符。默认情况下属性,方法,类等都是共有的,类似 Java 的 public。如果想要表示私有,则以下划线_开头去命名
2)、Dart 中实例化对象和 Java 类似,new 关键字可写可不写
3)、当我们在类中创建私有属性时,我们应该给私有属性提供 getter 和 setter 方法供外界访问:
get 方法语法格式:返回值类型 get 方法名 { 方法体 }
set 方法语法格式:set 方法名 ( 参数 ) { 方法体 }
class Person {
// 定义类成员属性,默认类的成员属性和方法都是共有的,类似 java 的 public
var name;
// 以下划线 ( _ ) 开头命名的属性代表私有成员属性
var _age;
// 跟类名同名的方法,为构造方法
// 这里自定义了一个携带参数的构造方法。
// 如果我们没有自定义构造方法,会自动生成一个不带参数的默认构造方法
Person(var name, var age) {
// 因为参数名和类属性名同名,可以使用this引用当前对象
this.name = name;
// 可以忽略this关键字,直接引用类成员
_age = age;
}
//为 _age 提供 getter 和 setter 方法
int get age{
return _age;
}
//getter 方法还可以简化为此写法:int get age => _age;
set age(int age){
_age = age;
}
// 定一个 public 的方法
String greet(String who) => 'Hello, $who. I am $name, my age is $_age !';
}
void main(){
var person = Person("erdai",18);
//下面这句就是调用了 age 的 set 方法
person.age = 20;
var greet = person.greet("lucy");
print(greet);
}
//打印结果
Hello, lucy. I am erdai, my age is 20 !
构造方法
如果我们没有自定义一个构造方法,会自动生成一个不带参数的默认构造方法
// 这个类会生成默认的构造方法
class Person {
String name;
}
// 通过默认构造方法实例化对象
var p = Person();
自定义构造方法
class Point{
var x,y;
Point(var x,var y){
// 通过this访问成员属性,当然一般除非出现命名冲突,否则可以忽略this
this.x = x;
this.y = y;
}
}
对于构造方法中,简单的赋值操作,Dart语言提供了更简洁的语法,如下:
class Point{
var x,y;
// 直接将构造方法的第一个参数赋值给this.x, 第二个参数赋值给this.y
Point(this.x,this.y);
}
初始化参数列表
Dart 还为构造方法提供了 参数初始化列表 的语法,用于初始化对象参数
class Point{
var x,y;
// 冒号 : 后面的表达式就是参数初始化列表,每个表达式用逗号分隔
Point(var x,var y): this.x = x,this.y = y{
// 使用参数初始化列表初始化对象属性,这里如果没有别的初始化工作要做,可以是空的
}
}
命名构造方法
1)、Dart 可以使用命名构造方法语法,创建多个构造方法,命名构造方法语法格式: 类名.构造方法名(参数列表)
class Point{
var x,y;
Point(this.x,this.y);
// 命名构造方法 namedConstructor
Point.namedConstructor(){
x = 0;
y = 0;
}
}
void main(){
// 使用命名构造方法实例化对象
var point = Point.namedConstructor();
}
上面的例子也可以改写为:
class Point{
var x,y;
Point(this.x,this.y);
// 命名构造方法 namedConstructor
// 这里使用参数初始化列表,直接通过 this 调用上面的构造方法,传入两个参数 0,初始化对象
Point.namedConstructor():this(0,0);
}
工厂构造方法
1)、Dart 提供了一个特殊的构造方法,类似设计模式中的工厂模式,用来创建对象
2)、factory 构造方法只能访问静态属性和静态成员方法,因此不能访问 this 引用
//1、定义个日志类
class Logger {
final String name;
bool mute = false;
// 定义一个私有的_cache属性,用来保存创建好的Logger对象
static final Map<String, Logger> _cache = {};
// 注意这个构造方法,前面使用了factory关键字修饰,这代表这个构造方法是一个工厂构造方法
// 工厂构造方法不会每次都创建一个新的Logger对象
factory Logger(String name) {
// 根据name判断缓存的Logger对象是否存在
if (_cache.containsKey(name)) {
// 返回缓存的Logger对象
return _cache[name]!;
} else {
// 如果没有缓存,则调用命名构造方法_internal创建一个Logger对象
final logger = Logger._internal(name);
// 根据name缓存logger
_cache[name] = logger;
// 返回新的Logger对象
return logger;
}
}
// 注意这个是一个私有的命名构造方法。
Logger._internal(this.name);
void log(String msg) {
if (!mute) print(msg);
}
}
//2、测试
void main(){
var logger = Logger("erdai");
logger.log(logger.name);
}
//打印结果
erdai
静态成员
跟 TS 一样,使用 static 来声明静态成员。
class Rect {
static int height = 10;
static int width = 10;
static getArea() {
print(height * width);
}
}
void main(List<String> args) {
Rect.getArea();
}
有两点需要注意:
-
静态成员不能访问实例变量
dart 复制代码 class Rect { int height = 10; static int width = 10; static getArea() { print(this.height * width); // 报错了 不能访问 实例属性 height } } -
实例方法可以访问静态成员
class Rect { int height; static int width = 10; Rect(this.height); getArea() { print(this.height * width);// 如果访问实例属性,推荐加上 this。 } } void main(List<String> args) { new Rect(10).getArea(); }
继承和多态
继承
构造函数不能被继承,使用 extends 和 super 关键字来继承父类的属性和方法。
- 纯继承父类
class Animal {
String name;
void sound(voice) {
print(voice);
}
Animal(this.name);
}
class Dog extends Animal {
Dog([String name = 'dog']) : super(name);
}
void main(List<String> args) {
var dog = new Dog();
print(dog.name); // dog
dog.sound('汪汪'); // 汪汪
}
其中Dog([String name = 'dog']) : super(name);需要解释一下:
-
: super(name)这种语法是用初始化列表在构造 Dog 时调用其父类的构造函数来设置name -
Dog([String name = 'dog'])这种语法是调用new Dog()时name是可选的,默认值为dog -
扩展子类的属性和方法
class Animal {
String name;
void sound(voice) {
print(voice);
}
Animal.create(this.name);
}
class Dog extends Animal {
String sex;
Dog(this.sex, [String name = 'dog']) : super.create(name);
void run() {
print('${this.name} runrun');
}
}
- 重写父类的属性和方法
class Animal {
String name;
void sound(voice) {
print(voice);
}
Animal.create(this.name);
}
class Dog extends Animal {
String sex;
Dog(this.sex, [String name = 'dog']) : super.create(name);
void run() {
print('${this.name} runrun');
}
@override
void sound(voice) {
print('${this.name} $voice');
}
}
void main(List<String> args) {
var dog = new Dog('雄');
print(dog.name); // dog
dog.sound('汪汪'); //dog 汪汪
}
推荐使用@override来重写父类的属性和方法
- 子类中调用父类的方法
通过 super 来调用父类的方法
class Dog extends Animal {
String sex;
Dog(this.sex, [String name = 'dog']) : super.create(name);
void run() {
super.sound('汪汪');
print('${this.name} runrun');
}
}
抽象类与多态
1)、抽象类就是不能实例化的类,通过 abstract 关键字声明
2)、抽象方法就是没有实现的方法,Dart 中的抽象方法不能用 abstract 声明,Dart 中没有方法体的方法就称为抽象方法
3)、继承抽象类,子类必须要实现所有抽象方法,否则会报错
// 使用 abstract 关键字修饰的类,就是抽象类
abstract class Doer{
// 抽象类跟普通类一样,可以定义成员变量,成员方法。
String name = "";
// 定义个抽象方法,这个方法我们没有实现具体的功能
void doSomething();
}
// 继承抽象类 Doer
class EffectiveDoer extends Doer{
// 实现抽象类的抽象方法
@override
void doSomething() {
print('doSomething');
}
}
void main(){
var doer = EffectiveDoer();
doer.doSomething();
doer.name = "erdai";
print(doer.name);
}
//打印结果
doSomething
erdai
接口
1)、Dart 中的接口没有使用 interface 关键字定义,而是普通类和抽象类都可以作为接口被实现。但是一般都是用抽象类来定义接口
2)、子类通过 implements 来实现接口
3)、默认情况每一个类都隐含一个包含所有公有成员(属性和方法)的接口定义
abstract class Fruit{
// 包含在隐式接口里面
String name = "";
// 构造方法不包含在隐式接口里面
Fruit(this.name);
// 包含在隐式接口里面
void eat();
}
class Apple implements Fruit{
@override
String name = "苹果";
@override
void eat() {
print('吃$name');
}
}
void main(){
var fruit = Apple();
fruit.eat();
}
//打印结果
吃苹果
实现多线接口
和Java中一样,dart中继承是单继承,但是实现接口是可以实现多个接口的(抽象类)
abstract class A {
late String name;
getA();
}
abstract class B {
getB();
}
class C implements A, B {
@override
getA() {}
@override
getB() {}
@override
late String name;
}
多态
多态就是同一操作作用于不同的对象时,可以产生不同的解释和不同的效果。Dart中的多态是通过子类重写父类定义的方法,这样每个子类都有不同的表现。
使用抽象类的话就只需要定义父类的方法而不用实现,让继承它的子类去实现,每个子类就是多态的。
abstract class Animal {
sound(); // 抽象方法
}
class Dog extends Animal {
@override
sound() {
print('汪汪');
}
run() {}
}
class Cat extends Animal {
@override
sound() {
print('喵喵');
}
run() {}
}
void main(List<String> args) {
var dog = new Dog();
var cat = new Cat();
print(dog.sound());
print(cat.run());
// 下面两个不能调 run 方法
Animal _dog = new Dog();
Animal _cat = new Cat();
}
minxins 混入
为什么需要minxins
其实,使用vue的各位大佬们对mixins应该不陌生,当然在vue3中使用组合式API进行了变相的替换,但是mixin这种思路是没有问题的,其实这也是现在编程语言的两种设计思路,既组合和继承,minxins显然是属于组合的。
对于上文所说的继承与多态,假设我们一个类——Animal,它有三个子类——Mammal、Bird及Fish,而这三个类也有其对应的子类,以及相应的方法,那么如下的实体用继承的方法实现将会变得非常冗余,如下:
//Dart代码
class Animal {...}
class Mammal extends Animal {...}
class Bird extends Animal {...}
class Fish extends Animal {...}
class Dolphin extends Mammal {...}
class Bat extends Mammal {...}
class Cat extends Mammal {...}
class Dove extends Bird {...}
class Duck extends Bird {...}
class Shark extends Fish {...}
class FlyingFish extends Fish {...}
//行走行为
abstract class Walk{
walk();
}
//游泳行为
abstract class Swim{
swim();
}
//飞翔行为
abstract class FLying {
flying();
}
//海豚可以游泳
class Dolphin extends Mammal implements Swim {
@Override
public void swim() {...}
}
//蝙蝠可以飞、行走
class Bat extends Mammal implements Flying,Walk {
@Override
void walk() {...}
@Override
void flying() {...}
}
//猫可以行走
class Cat extends Mammal implements Walk {
@Override
void walk() {...}
}
//鸽子可以行走、飞
class Dove extends Bird implements Walk,Flying {
@Override
void walk() {...}
@Override
void flying() {...}
}
//鸭子可以行走、飞及游泳
class Duck extends Bird implements Walk,Flying,Swim {
@Override
void swim() {...}
@Override
void walk() {...}
@Override
void flying() {...}
}
//鲨鱼可以游泳
class Shark extends Fish implements Swim {
@Override
void swim() {...}
}
//飞鱼可以游泳、飞
class FlyingFish extends Fish implements Swim,Flying {
@Override
void swim() {...}
@Override
void flying() {...}
}
``Walk,Swim及Flying。由于这些行为并不是所有类通用的,所以不能将这些行为放在父类, 所以每个类都得实现一遍,但是代码是一样的!!!
mixins 用法
下面将上面的继承关系用mixins实现将会变得简洁很多
//行走
mixin Walker{
void walk(){...}
}
//游泳行为
mixin Swim{
void swim(){...}
}
//飞翔行为,由于这个是抽象方法,所以必须在要实现,不能调用super.flying()
mixin Flying {
void flying();
}
//海豚可以游泳
class Dolphin extends Mammal with Swim{
@override
void swim() {
super.swim();
}
}
//蝙蝠可以飞、行走
class Bat extends Mammal with Flying,Walk{
@override
void flying() {...}
//覆盖Walk类中的walk方法
@override
void walk() {
super.walk();
}
}
//猫可以行走,这里没有重写Walk中的方法
class Cat extends Mammal with Walk{}
//鸽子可以行走、飞
class Dove extends Bird with Flying,Walk{
@override
void flying() {...}
}
//鸭子可以行走、飞及游泳
class Duck extends Bird with Walk,Flying,Swim{
@override
void flying() {...}
@override
void walk() {...}
}
//鲨鱼可以游泳
class Shark extends Fish with Swim{...}
//飞鱼可以飞及游泳
class FlyingFish extends Fish with Flying,Swim{
@override
void flying() {...}
}
mixins线性化
在上面的示例中,我们发现with关键字后有多个类。那么这里就产生了一个问题——如果with后的多个类中有相同的方法,那么当调用该方法时,会调用哪个类里的方法尼
- 如果当前使用类重写了该方法,就会调用当前类中的方法。
- 如果当前使用类没有重写了该方法,则会调用距离
with关键字最远类中的方法
//BindingBase
abstract class BindingBase {
void initInstances() {
print("BindingBase——initInstances");
}
}
//GestureBinding
mixin GestureBinding on BindingBase{
@override
void initInstances() {
print("GestureBinding——initInstances");
super.initInstances();
}
}
//RendererBinding
mixin RendererBinding{
@override
void initInstances() {
print("RendererBinding——initInstances");
super.initInstances();
}
}
// WidgetsBinding
mixin WidgetsBinding on BindingBase
{
@override
void initInstances() {
print("WidgetsBinding——initInstances");
super.initInstances();
}
}
//WidgetsFlutterBinding
class WidgetsFlutterBinding extends BindingBase
with GestureBinding, RendererBinding, WidgetsBinding {
static WidgetsBinding ensureInitialized() {
return WidgetsFlutterBinding();
}
}
//main
main(List<String> arguments) {
var binding = WidgetsFlutterBinding();
binding.initInstances();
}
可以看到,最终调用的是WidgetsBinding.initInstances(), 我们在
WidgetsBinding、RendererBinding及GestureBinding中都调用了父类的initInstances方法,也因此会一级一级往上调用
将其最终实现用一张图表示如下
class S {
fun()=>print('A');
}
mixin MA {
fun()=>print('MA');
}
mixin MB {
fun()=>print('MB');
}
class A extends S with MA,MB {}
class B extends S with MB,MA {}
Dart 中的 mixin 通过创建一个类来实现,该类将 mixin 的实现层叠在一个超类之上以创建一个新类,因此而实现了一条继承链,如上图所示。
下面再看一个例子
class S { fun()=>print('A'); }
mixin MA on S {
fun() {
super.fun();
log();
print('MA');
}
log() {
print('log MA');
}
}
mixin MB on S {
fun() {
super.fun();
print('MB');
}
log() {
print('log MB');
}
}
class A extends S with MA,MB {}
A a = A();
a.fun();
输出结果如下
A
log MB
MA
MB
仔细思考一下,按照上面的工作原理,在 mixin 的继承链建立的时候,最后声明的 mixin 会把后声明的 minxin 的函数覆盖掉。这时候即使我们从代码逻辑中认为在 MA 中调用了 log 函数,实际上这时候 A 类中的 log 函数已经被 MB 给覆盖了。所以最终,log 函数调用的是 MB 中的 log 函数逻辑
注意事项
-
被 mixins 的类只能继承自 Object,不能继承其他类。
class A { void getA() {} } class B extends A { void getB() {} } class C with A, B {} // ❌报错,B 是被 mixins 的类,不能继承为了让 mixins 类更加直观,推荐使用 mixin 关键字来定义
mixin类mixin A { void getA() {} } mixin B extends A { // ❌报错,B 是被 mixins 的类,不能继承 void getB() {} } class C with A, B {} -
被 mixins 的类不能有构造函数
mixin A { void getA() {} } mixin B { B(); // ❌报错 B 是被 mixins 的类,不能有构造函数 void getB() {} } class C with A, B {} -
一个类可以 mixins 多个 mixins 类
-
一个类可以继承某个类再 mixins 一些 mixins 类
class A { void getA() {} } class B { void getB() {} } class C extends A with B {} -
mixins 不是继承,也不是接口,当使用 mixins 后,相当于创建了一个超类,能够兼容下所有类
class A { void getA() {} } mixin B { void getB() {} } class C extends A with B {} void main(List<String> args) { var c = new C(); print(c is A);// true print(c is B);// true print(c is C);// true } -
使用 on 关键字可以指定哪些类可以使用该 Mixin 类
class A { void getA() {} } mixin B on A { void getB() {} } // class C with B {} ❌这样写是报错的 class C extends A with B {}
泛型
跟 TS 一样,Dart 也支持泛型,泛型就是泛用的类型,是一种将指定权交给用户的不特定类型。
比如下面的函数就由用户指定传入的类型。
T getData<T>(T data) {
return data;
}
// 调用者可以指定类型
getData<String>('123');
getData<num>(123);
getData<List>([1, 2, 3]);
泛型类
在实例化一个类时可以通过泛型来指定实例对象的类型。
下面就是实例化 List 后指定了List 对象属性值的类型。
List l1 = new List<int>.filled(2, 1);
List l2 = new List<String>.filled(2, '');
-
定义泛型类
class A<T> { T age; A(T this.age); T getAge() { return this.age; } } -
使用泛型类
void main(List<String> args) { // 使用泛型类 var a = new A<int>(12); var b = A<String>('12'); }
泛型接口
泛型接口的定义方式就是接口跟泛型类的集合体,可以这么定义
// 泛型接口
abstract class Cache<T> {
void setKey(String key, T value);
}
// 类匹配这个接口
class FileCache<T> implements Cache<T> {
@override
void setKey(String key, T value) {}
}
class MemoryCache<T> implements Cache<T> {
@override
void setKey(String key, T value) {}
}
使用时指定泛型的具体类型
var f = new FileCache<String>();// 指定 String
f.setKey('key', 'string');
var m = new MemoryCache<int>();// 指定 int
m.setKey('key', 123);
限制泛型
跟 Typescript 一样,泛型约束使用 extends 关键字。
abstract class Cache<T> {
void setKey(String key, T value);
}
// 这里约束MemoryCache只能为 int
class MemoryCache<T extends int> implements Cache<T> {
@override
void setKey(String key, T value) {}
}
void main(List<String> args) {
// var m = new MemoryCache<String>(); 这里就不能是 String 类型了
var m = new MemoryCache<int>();
m.setKey('key', 123);
}