啰啰嗦嗦的前言
软件发展的历史印证了应用层开发必然是OOP(Object Oriented Programming)(面向对象编程语言)语言的天下,安卓应用开发之前一直是使用jvm系语言(最开始是java,后来又有kotlin),但是java由于需要兼顾全平台,很多地方并不是为移动端量身定做,这就导致了某些方面无法做到最优,再加上java易主后oracle跟谷歌在版权问题上的各种不愉快,可能就导致了谷歌想降低安卓平台对java的依赖,后来就有了flutter,flutter使用dart语言。
扯远了,关键字只有OOP,相比java,dart是更彻底的OOP,一切都是对象。那么就从类和对象学起。
类的定义
写法跟JVM系语言都差不多,随便写一个:
abstract class Animal{
//普通构造器
Animal(String name, int age):
assert(name != null),
assert(age > -1),
this.name = name, this.age = age{
print("Animal's constructor is invoked");
}
//命名构造器的转发写法,调用自己的其他构造器
//注意一旦这么写了,后面就不能接函数体了
Animal.fromMap(Map map):
this(map["name"] ?? "unknownAnimal", map["age"] ?? 0);
//命名构造器的非转发写法,可以接函数体
Animal.fromDynamic(dynamic object){
this.name = object.name;
this.age = object.age;
}
String name;
int age;
//来个抽象方法,这里不写abstract,没有方法体的方法就是抽象方法。
//动物当然得会动才行
void move();
}
上面的普通构造器其实可以简化,变成这样:
Animal(this.name, this.age):
assert(name != null),
assert(age > -1){
print("Animal's constructor is invoked");
}
这种写法的含义是,构造器接受两个跟自己的域成员类型一致的变量,分别赋值给它们,因为我们定义的name是String,age是int,所以这里就需要第一个参数是String而第二个参数是int,assert语句会在赋值之前被执行。
注意:普通构造器的名字跟类名相同,而且每个类只能有一个普通构造器
。
下面这种写法用来让所有对构造器的调用都产生相同的对象:
class Planet{
static String description = "这是一个行星,一旦命名就不会改名";
static int orbitInvokedCount;
final String name;
const Planet(this.name);
void showOrbit(){
print("本来应该让你看到我运行的轨道,但想想还是算了");
print("算上你,一共${++orbitInvokedCount}个傻帽被我气的发飙");
}
}
如果这么写,类里面所有非静态成员都必须是final。这种类也是可以被继承的。
继承(extends)
随便写个子类:
class Rabbit extends Animal{
Rabbit(String name, int age) : super(name, age);
@override
void move() {
print("兔子蹦蹦跳跳的往前走");
}
}
一个类如果不显示指定构造器,那么它就有一个无参的普通构造器,如果指定了有参构造器,子类就必须在它和其他命名构造器里选择一个实现(如果有命名构造器),以上例子我选择了实现普通构造器,当然也可以实现命名构造器:
class Rabbit extends Animal{
Rabbit.fromMapToo(Map map) : super.fromMap(map);
@override
void move() {
print("兔子蹦蹦跳跳的往前走");
}
}
可以看到,命名构造器不一定要跟父类同名,简单来说,有办法能把这个类实例化就行。不管是普通构造器还是什么途径都行。
实现(implements)
注意:在dart里,一个类在定义的时候就同时产生了两个东西,一个是你写的完整定义以及行为
,另一个是由他们抽象出来的接口
,也就是说,当我定义了兔子类的时候,这个东西就同时代表两个概念:兔子
这个类和像兔子的
这个接口,它们的名字都是Rabbit。跟java不同,这里类的属性也是接口的一部分,估计“得益于”getter、setter语法糖。。。(无奈脸)
class RabbitDoll implements Rabbit{
@override
int age = 0;
@override
String name = "兔子娃娃";
//这个类的构造函数不受Rabbit的约束
RabbitDoll(this.age, this.name);
RabbitDoll.fromWhat();
@override
void move() {
print("兔子布偶不会走");
}
}
混合(mixin)
混合的概念是把类的方法放入另一个类,这在jvm语言上应该是以组合的形式实现的,dart具体实现方式不明,上代码说明:
class Flyable{
String trait = "会飞";
void move(){
print("老子会飞");
}
void showTrait(){
print("这个东西的特征是$trait");
}
}
class Sparrow with Flyable implements Animal{
@override
int age;
@override
String name;
}
class Dove extends Animal with Flyable{
Dove(String name, int age) : super(name, age);
}
之前定义的animal类有一个抽象方法move,这个方法已经被Flyable实现,下面的Sparrow和Dove类分别以implements和extends的方式获得Animal的接口特征,具体实现都委托给被混入的Flyable了。
Dart的类型系统里是单继承
、多实现
、多混合
,凡是带“多”的都是逗号分隔,这就势必考虑到一个问题那就是冲突问题,implements里不存在具体实现所以没有冲突问题,而多mixin的情况由于mixin类都有具体实现,如果一个特征相同的方法在多个mixin里都有实现那会如何?实验证明,写在后面的mixin会覆盖前面的实现。你调用这个方法时以最后的mixin为准,当然,前提是你方法体里不再次覆盖这个方法。而mixin的优先级又是高于父类实现的。
一句话总结:方法的优先级:自己的 > mixin列表[n]的 > mixin列表[0]的 > 父类的
。一旦冲突,优先级低的就被覆盖掉。
mixin其实还有一个写法,是用mixin关键字定义:
mixin AnimalLike implements Animal{
}
这种写法跟class的区别是:无法被实例化,不能extends其他类,只能implements接口,可以包含抽象成员。它可以使用on
关键字限定只有特定类(及其子类)能混合它:
mixin AnimalLike on Dove implements Animal{
}
如果这么写了,只有Dove和Dove的子类可以with它,当然,on关键字后面是可以跟多个类的,用逗号分隔。
注意:这里说的是子类而不是接口,如果你用on限定了一个类,类X implements了它,那X是没法with这个mixin的。
一个类继承A混合了BCD,它的类型到底是什么?
这个问题其实没什么意义,之前说过,dart在声明一个类的时候就同时产生了同名的接口,而无论是is操作符(对应java的instanceof)还是泛型,里面写的其实都是上述的同名接口,我们在使用这些对象时关心的也只是它的方法特征(输入输出)而不是具体实现。
但如果硬要说的话,按java里那种父类的概念,子类的构造器要追随父类这倒是没错的。
var sparrow = Sparrow();
var dove = Dove("鸽子", 1);
sparrow.move();
sparrow.showTrait();
var animals = List<Animal>();
var flyables = List<Flyable>();
animals.add(sparrow);
flyables.add(sparrow);
animals.add(dove);
flyables.add(dove);
dove和sparrow既是Animal也是Flyable,不同的场景关注点不同,我们想联系一个朋友,那么微信和电话都能实现这个功能,这里我们就只关注通讯这个功能,至于微信还能做什么其他的,在这个应用场景就被忽略了。
访问限定修饰符
在dart里以_开头的是私有成员,跟es6一样,没什么好说的。