Flutter之旅:从源码赏析Dart面向对象

前言

相信大家都是有过面向对象经验的人,那面向对象是什么感觉呢?
大概也就是一开始心跳加速,小鹿乱撞,之后平淡无奇,最后被她折磨到怀疑人生。
今天给你介绍个对象,她的名字叫Dart,还等什么,赶快认识一下。

image

1.面向对象的条件

1.1:三大特性

首先房子、车子、票子要有的吧,不然还面个什么对象?其次面向对象思想要到位,准备的三大件:封装、继承、多态

1.1.1:封装的思想

下面这东西大家应该见过,是一个电子元件,它能很好地说明封装的特性。每个电子元件都有暴露在外的引脚。这些脚分为输入和输出。使用一个电子元件时,我们只是关心输入和输出的情况,也就是它的真值表,对他内部的构成是不关心的。
所以一个电子元件对硬件组装师而言,就是一个有引脚的黑块,一个能够完成特定功能的逻辑单元。类比一下三方类库,在引入之后,不需要知道库的具体实现逻辑,只要按照暴露的API(真值表),你进行一个API的调用(输入),就会完成特定的功能(输出)。

image
image

类也是这样,最终目的是要实现逻辑的复用,让其成为一个可独立存在的逻辑单元,从而和其他类共同运作。但是现在与上面所不同的是:我们不单单是一个使用者,更多角色的是它的设计者。所以需要考虑的要点不仅是此类和其他类如何契合运作,更重要的是它的内部构成。
要知道,使用一个电子元件,和设计制造一个电子元件是完全不同的,而程序设计师显然是后者。


1.1.2:继承的思想

一个人的出生并非是一无所有,它享有着父母的资产,人脉,地位。这些都是他可以使用的资源。 这就无需艰苦奋斗来达到当前的境地,从而能够在未来的发展中更上一层。当然,类也是这样,子类通过继承可以享受到父类所带来的'天赋'。

这就涉及到了一个概念,叫抽象。抽象并非随便抽的,不以解决问题为前提的抽象都是耍流氓,通过抽象来提取对象的公共特征,形成基类。这相当于是父母在努力打拼,想为孩子打造一个适宜的生存环境。

还有一点就是:孩子不一定继承父亲的一切,有些东西是父亲不想给孩子的,这就涉及到继承的访问限制。


1.1.3:类的多态

一个人在社会中可以拥有多个角色,比如捷特在学校是一个学生、在公司是程序员、在周末是一个男朋友、在旅行中是一个游客,这就是一个对象的不同状态,简称:多态。
多态有什么优势,比如:有人喊,来个学生搬桌子,捷特就可以以学生的身份过去;周末妹子约,捷特就可以男朋友的身份过去;有人喊,来个程序员敲改bug,捷特也能跑过去。
下面写段伪代码,这样的优势在于使用对象的那个方法只需要针对接口即可,并不需要传入Me对象。如果pickDesk,date,fixBug全部传入Me对象,虽然能运行,但是不利于拓展和维护。

class Me implements Pickable,Kissable,Codeable{

}

pickDesk(Pickable student){
  student.pick(this);
}

date(Kissable boyfriend){
  if(boyfriend.getId == this.id){
    boyfriend.kiss(this);
  }
}

fixBug(Codeable coder){
  coder.debug(this);
}

好了,罗里吧嗦一堆,下面开始进入正题,给你介绍Dart的面向对象,一般说面向对象都是个Person,在加个Student什么的。
虽说没什么不好,但感觉很平淡无奇,既然咱们都是有面向对象经验的人,直接去源码里找对象面面呗,这样才惊险,刺激。


2.从Size一族开始说起

我一直在想通过那个类的源码开始说比较好,最好不要太长,也不要太难,Size就比较完美。

2.1.类的定义

class 关键字定义类,没毛病,extend 继承关键字,也很OK。

---->[sky_engine/lib/ui/geometry.dart:347]-------
class Size extends OffsetBase {

2.2.抽象类的定义

Size继承自OffsetBase,那就来看一下OffsetBase。首先它是一个抽象类,使用的关键字也是abstract。
其中有一个构造方法,传入两个参数,分别是水平和竖直的值。注意构造方法的书写形式是和Java有所区别的。
其中封装了两个私有属性:_dx_dy

---->[sky_engine/lib/ui/geometry.dart:9]-------
abstract class OffsetBase {
  const OffsetBase(this._dx, this._dy);//构造函数

  final double _dx;
  final double _dy;
}

2.3:方法

不知大家看到下面的代码感觉如何,反正我感觉挺别扭,也许不太熟悉吧。

//是否无法衡量的
bool get isInfinite => _dx >= double.infinity || _dy >= double.infinity;
//是否有限
bool get isFinite => _dx.isFinite && _dy.isFinite;

看着有点吓人,不过才刚开始,可以慢慢分析,上面这句如果看得眼花缭乱,
我改写了一下,下面的应该可以看懂吧,意思就是如果_dx和_dy有一个超过double的范围就返回true

bool isInfinite() {
    return _dx >=double.infinity || _dy >= double.infinity;
}

注意一点:在Java中常用isXXX,Dart 里的get关键字可以让调用简洁,使用如下
var size= Size(30,40);
print(size.isInfinite);//false

2.4:运算符重载

这段代码看了三秒钟,箭头太多,愣是没反应过来,仔细一想,这不会是运算符重载吧,和C++的有点神似。下面是分别对<、<=、>、>=、==的运算符重载。


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(30,40);
var size2= Size(10,20);
print(size>size2);//true

2.5:哈希值和toString

和Java比较像,没啥好说的。这里要插一句,看源码的时候,最好自己留心一下源码中的书写风格,毕竟和大佬看起是很必要的,比如命名的风格,注释的风格等。

@override
int get hashCode => hashValues(_dx, _dy);

@override
String toString() => '$runtimeType(${_dx?.toStringAsFixed(1)}, ${_dy?.toStringAsFixed(1)})';

OK,这样一个OffsetBase对象就面完了,紧不紧张,刺不刺激。下面继续看Size


2.6:构造Size对象方法

注意了,这里圈起来,要考的。使用父类的构造方法来完成本类的构造个语法格式:类名(参数,...):super(参数,...)

  
class Size extends OffsetBase {
  /// 根据给定的宽高创建Size对象
  const Size(double width, double height) : super(width, height);

在联想一下初始项目中的让人蒙圈的这句话,是不是豁然开朗。

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

然后就是一堆构造Size对象的方法,总的来说就是,不管怎样,你给我弄两个值来当宽高

  Size.copy(Size source) : super(source.width, source.height);
  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);

2.7:Size中的属性封装

有了老爹之后,Size类就可以使用老爹的“财富”,这里Size为了形象,将老爹的东西名字都给改了。
类存在的价值之一在于封装属性及调动属性之间的关系完成特定功能,比如aspectRatio可以获取宽高比。

对于任意Size对象,在任意时间,任意空间,都可以调用aspectRatio方法获取宽高比,这是面向过程所不能及的。

  double get width => _dx;
  double get height => _dy;

  double get aspectRatio {
    if (height != 0.0)
      return width / height;
    if (width > 0.0)
      return double.infinity;
    if (width < 0.0)
      return double.negativeInfinity;
    return 0.0;
  }

2.8:继承的优势

上面OffsetBase说到运算符重载,在Size类中也有运算符重载,这些是尺寸的四则运算,然而Size依旧可以使用OffsetBase中重载过的运算符,这就是有老爹的优势。

OffsetBase operator -(OffsetBase other) {
  if (other is Size)
    return new Offset(width - other.width, height - other.height);
  if (other is Offset)
    return new Size(width - other.dx, height - other.dy);
  throw new ArgumentError(other);
}
Size operator +(Offset other) => new Size(width + other.dx, height + other.dy);
Size operator *(double operand) => new Size(width * operand, height * operand);
Size operator /(double operand) => new Size(width / operand, height / operand);
Size operator ~/(double operand) => new Size((width ~/ operand).toDouble(), (height ~/ operand).toDouble());
Size operator %(double operand) => new Size(width % operand, height % operand);
2.9.看Offset类

继承会让孩子具有先天优势,那么两个孩子岂不是更加物尽其用。定义一个父类就是为了能够更好的拓展,OffsetBase自然也不例外。
Size对象描述了一个类似框框的对象,那么Offset描述的便是位移。两者有一个共同的特点,那就是有两个数值类的属性。这也是OffsetBase被抽象出来的原因。

class Offset extends OffsetBase {
  //构造方法
  const Offset(double dx, double dy) : super(dx, dy);
  factory Offset.fromDirection(double direction, [ double distance = 1.0 ]) {
    return new Offset(distance * math.cos(direction), distance * math.sin(direction));
  }
  
  static const Offset zero = const Offset(0.0, 0.0);
  static const Offset infinite = const Offset(double.infinity, double.infinity);

  double get dx => _dx; //水平位移
  double get dy => _dy; //数值位移
  
  //方法:属性的处理
  double get distance => math.sqrt(dx * dx + dy * dy);//移动距离
  double get distanceSquared => dx * dx + dy * dy;//移动距离的平方
  Offset scale(double scaleX, double scaleY) => new Offset(dx * scaleX, dy * scaleY);//缩放
  Offset translate(double translateX, double translateY) => new Offset(dx + translateX, dy + translateY);//移动
  
 //运算符重载
  Offset operator -() => new Offset(-dx, -dy);
  Offset operator -(Offset other) => new Offset(dx - other.dx, dy - other.dy);
  Offset operator +(Offset other) => new Offset(dx + other.dx, dy + other.dy);
  Offset operator *(double operand) => new Offset(dx * operand, dy * operand);
  Offset operator /(double operand) => new Offset(dx / operand, dy / operand);
  Offset operator %(double operand) => new Offset(dx % operand, dy % operand);

  Rect operator &(Size other) => new Rect.fromLTWH(dx, dy, other.width, other.height);

  //略....

  @override
  int get hashCode => hashValues(dx, dy);

  @override
  String toString() => 'Offset(${dx?.toStringAsFixed(1)}, ${dy?.toStringAsFixed(1)})';
2.10:小结

这是三个类比较简单,很适合刚入门Dart的伙伴读读,关系简单画一下。
现在你应该对Dart中类的创建,属性,方法的书写以及类的继承有所理解了吧。

image

3.Dart中的接口与枚举

与Java不同,Dart中的接口定义依然是abstract关键字,接口和抽象类本质上并没有区别,都是对一类事物的抽象,只不过接口更倾向于提取事物的能力。比如Comparable接口和Pattern接口。

3.1 :接口的定义

Comparable定义了一个抽象方法compareTo,用来和另一个对象进行比较,也就是这个接口的功能是作比较。
Pattern接口的功能是:匹配字符串,获得一个可迭代的匹配结果集。

---->[sky_engine/lib/core/comparable.dart:72]----------
abstract class Comparable<T> {

  int compareTo(T other);
  
  static int compare(Comparable a, Comparable b) => a.compareTo(b);
}

---->[sky_engine/lib/core/pattern.dart:72]----------
abstract class Pattern {
  Iterable<Match> allMatches(String string, [int start = 0]);
  Match matchAsPrefix(String string, [int start = 0]);
}

3.2:Dart中的接口

实现接口的类拥有该接口的功能。可以看出num是实现了Comparable接口的,可以说明它有作比较的能力。
接口的实现和Java一样,是关键字implements。

abstract class num implements Comparable<num> {


3.3:实现多个接口

Dart中的接口也是支持多实现的,用逗号隔开。比如String即实现Comparable,也实现了Pattern,
说明String同时可具有这两种功能。

abstract class String implements Comparable<String>, Pattern {

3.4:枚举类型

枚举通常用来表示相同类型的一组常量,用关键字enum来表示。
枚举对象可以结合switch做分支处理。
另外Dart中的枚举元素具有索引,从0开始,依次计数,用index属性访问。
说到枚举,我首先想到的就是Paint的头,就用这个类来说明一下:

image
---->[sky_engine/lib/ui/painting.dart:833]----------
enum StrokeCap {
  butt,//无头(默认)
  round,//圆型
  square,//方形
}

使用:
var paint= Paint();
paint.strokeCap=StrokeCap.round;//圆型
print(StrokeCap.round.index);//1

看来源码中的几个类,面向对象也基本上能有个认识。下面来自定义个二位向量总结一下。


4.自定义向量类Vector2

4.1:定义Vector2类与使用

这里定义了Vector2类,包含两个数值分别是横纵坐标,构造函数用最原始的方式

class Vector2{
  num x;
  num y;

  Vector2(num x, num y) {
    this.x = x;
    this.y = y;
  }
}

main(){//使用
  var v1=Vector2(3,4);
  print('(${v1.x},${v1.y})');//(3,4)
}

4.2:构造函数的简写

通过简写,可以使构造函数简洁明了,同时也能实现等价功能。

class Vector2{
  num x;
  num y;
  Vector2(this.x,this.y);
}

4.3:命名构造函数

源码中经常会出现XXX.formXXX来构造对象

class Vector2{
  num x;
  num y;
  Vector2.fromMap(Map<String,num> point){
    this.x = point['x'];
    this.y = point['y'];
  }
}

main(){//使用
  var v2= Vector2.fromMap({'x':5,'y':6});
  print('(${v2.x},${v2.y})');//(5,6)
}

4.4:定义方法
  double get distance => sqrt(x * x + y * y); //向量的模
  double get rad => atan2(y, x); //与x正方向夹角(弧度制)
  double get angle => 180 / pi * atan2(y, x); //与x正方向夹角(角度制)

4.5:定义一个操作接口
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]);//旋转
}

4.6:继承接口,实现方法
class Vector2 implements Operable{
  num x;
  num y;
  Vector2(this.x,this.y);

  Vector2.fromMap(Map<String,num> point){
    this.x = point['x'];
    this.y = point['y'];
  }

  @override
  void reflex() {
    this.x=-x;
    this.y=-y;
  }

  @override
  void reflexX() {
    this.x=-x;
  }

  @override
  void reflexY() {
    this.y=-y;
  }

  @override
  void rotate(num deg, [isAnticlockwise = true]) {
    var curRad = rad + deg*pi/180;
    this.x=distance*cos(curRad);
    this.y=distance*sin(curRad);
  }

  @override
  void scale(num xRate, num yRate) {
    this.x *=xRate;
    this.y *=yRate;
  }

  @override
  void translate(num dx, num dy) {
    this.x +=dx;
    this.y +=dy;
  }
  
  @override
  String toString() {
    return '(${this.x},${this.y})';
  }
}

4.7:运算符重载

现学现卖,运算符重载一下

//运算符重载
Vector2 operator +(Vector2 other) => Vector2(x + other.x, y + other.y);
Vector2 operator -(Vector2 other) => Vector2(x - other.x, y - other.y);
num operator *(Vector2 other) => x * other.x + y * other.y;

这个类就先这样,以后有需要继续改进。


4.8:测试
main() {
  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
}

本文到此接近尾声了,另外本人有一个Flutter微信交流群,欢迎小伙伴加入,共同探讨Flutter的问题,本人微信号:zdl1994328,期待与你的交流与切磋。如果想快速尝鲜Flutter,《Flutter七日》会是你的必备佳品。

下一篇将把语法扫个尾,介绍Dart中的其他语法。

最后说一句:祝大家早日能够真正的面向你的对象来编程👻

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容