2.2 通过Size类看Dart中的面向对象
开胃菜已经吃完了,下面来看正餐。Dart语言中的面向对象思想和其他语言一样,类的封装、继承、多态这三大特性应该是每个编程者都熟悉的。我们直接去Flutter的源码库中看一个简单的Size类,一边看源码,一边学习Dart中关于类的语法。Size及其相关类如下图所示:
对象的构造器
Size类在ui包的geometry类中,说明它和UI界面以及几何有关。类的定义使用class关键字,继承使用extends关键字,这和Java是一致的。Dart里通过类名(变量,变量,...)来实现构造函数。用:super()表示调用父类构造,并将宽高传递进去。比如创建宽高各为100的Size类,会在OffsetBase的构造方法中对成员进行初始化:
---->[lib/ui/geometry.dart#Size ]------- class Size extends OffsetBase { const Size(double width, double height) : super(width, height); ---->[lib/ui/geometry.dart#OffsetBase]------- abstract class OffsetBase { const OffsetBase(this._dx, this._dy); final double _dx; final double _dy;
除此之外,Size类还有很多其他的命名构造,通过类名.方法名()的形式来构造对象,总之就是为了初始化两个值——长和宽,二者都是依靠父类构造实现的:
const Size.square(double dimension) : super(dimension, dimension); const Size.fromWidth(double width) : super(width, double.infinity); const Size.fromHeight(double height) : super(double.infinity, height); const Size.fromRadius(double radius) : super(radius * 2.0, radius * 2.0); static const Size zero = const Size(0.0, 0.0); static const Size infinite = const Size(double.infinity, double.infinity);
属性和方法的封装
Dart语言中的抽象类通过abstract关键字来定义。下面的OffsetBase封装了两个变量,Dart语言中并没有限定权限的关键字,语法规定名字前加了下划线则说明是私有属性,其他文件无法直接访问它。这里在构造函数中通过this.属性,就可以对属性进行初始化。get关键字是一种特有语法,在调用时就像访问属性一样:
---->[lib/ui/geometry.dart#OffsetBase]------- abstract class OffsetBase { const OffsetBase(this._dx, this._dy); final double _dx; final double _dy; bool get isInfinite => _dx >= double.infinity || _dy >= double.infinity; bool get isFinite => _dx.isFinite && _dy.isFinite; ---->[使用]---- var size=Size(100,100); print(size.isInfinite);//false 是否无限大
类的运算符重载
第一次看到下面这些代码可能会比较懵,符号多到眼花缭乱。仔细一想,这不是运算符重载吗?operator关键字和运算符联合起来对运算符的功能进行重新定义,这样可以轻松地运算两个Size对象,实在是太酷了:
bool operator <(OffsetBase other) => _dx < other._dx && _dy < other._dy; bool operator <=(OffsetBase other) => _dx <= other._dx && _dy <= other._dy; bool operator >(OffsetBase other) => _dx > other._dx && _dy > other._dy; bool operator >=(OffsetBase other) => _dx >= other._dx && _dy >= other._dy; @override bool operator ==(dynamic other) { if (other is! OffsetBase)//如果传入的对象不是OffsetBase return false;//直接忽略 final OffsetBase typedOther = other; return _dx == typedOther._dx &&//判断是否相等 _dy == typedOther._dy; } ---->[使用]---- var size=Size(100,100); var size2= Size(10,20); print(size>size2);//true
类的继承与拓展
通过AS查看类层次,可以看到OffsetBase中还有一个Offset子类。Size类中的成员变量命名为width和height,而Offset类是dx和dy,都是两个double类型的,但Size是一个尺寸类,而Offset是一个偏移类。两者的本质区别在于功能不同,Offset中有distance、direction、scale、translate等有关偏移的方法。Size中则是可以获取宽高比,判断是否为空区域等的方法:
---->[lib/ui/geometry.dart#Offset]------- double get distance => math.sqrt(dx * dx + dy * dy); double get distanceSquared => dx * dx + dy * dy; double get direction => math.atan2(dy, dx); Offset scale(double scaleX, double scaleY) => Offset(dx * scaleX, dy * scaleY); Offset translate(double translateX, double translateY) => Offset(dx + translateX, dy + translateY);
提示:使用AS读源码时,需要查看接口或抽象类的衍生类,可以将光标放在类上,在Windows中按Ctrl+Alt键并单击,在MAC中按Cmd+Alt键并单击,会出现其所有子类,这样可以方便认识一个抽象类的衍生类。另外,在一个方法上按Cmd+Alt键并单击,也会展现选择该方法的衍生类列表:
接口的定义和使用
关于面向对象,还有一个非常重要的知识点就是接口,Dart语言中的接口是abstract关键字,这和Java有所不同。接口是对事物在行为能力方面的抽象。一个接口便是一组功能保障,实现一个接口就表明该类具有接口中定义的功能。在设计类或方法时面向接口,根据多态的特性,在使用时提供具体实现。下面定义了几个操作的接口方法:
---->[day02/04/vector.dart]--- abstract class Operable{ void reflex();//反向 void reflexX();//X反向 void reflexY();//Y反向 void scale(num xRate,num yRate);//缩放 void translate(num dx,num dy);//平移 void rotate(num deg,[isAnticlockwise=true]);//旋转 }
例如,自定义一个向量类Vector2,让其实现Operable接口,并实现相应的方法,在该类中可实现命名构造、属性获取(如长度、与x轴的夹角等)、运算符重载等:
使用时非常简单,通过Vector2.formMap可以将map转化为向量,通过reflex可以将该向量进行反向,重载运算符可以直接运算两个向量:
var v1 = Vector2(3, 4); print(v1); //(3,4) print(v1.distance); //5.0 print(v1.angle); //53.13010235415598 v1.rotate(37); print(v1);//(-0.011353562466313798,4.0000058005648444) var v2 = Vector2.fromMap({'x': 5, 'y': 6}); print(v2); //(5,6) v2.reflex(); print(v2);//(-5,-6) var v3 = Vector2(2, 2); var v4 = Vector2(3, 2); print(v4 - v3); //(1,0) print(v4 + v3); //(5,4) print(v4 * v3); //10