Flutter之旅
上QQ阅读APP看书,第一时间看更新

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