Flutter之旅:從源碼賞析Dart面向對象

前言

相信你們都是有過面向對象經驗的人,那面向對象是什麼感受呢?程序員

大概也就是一開始心跳加速,小鹿亂撞,以後平淡無奇,最後被她折磨到懷疑人生。
今天給你介紹個對象,她的名字叫Dart,還等什麼,趕快認識一下。編程


1.面向對象的條件

1.1:三大特性

首先房子、車子、票子要有的吧,否則還面個什麼對象?其次面向對象思想要到位,準備的三大件:封裝、繼承、多態微信

1.1.1:封裝的思想

下面這東西你們應該見過,是一個電子元件,它能很好地說明封裝的特性。每一個電子元件都有暴露在外的引腳。這些腳分爲輸入和輸出。使用一個電子元件時,咱們只是關心輸入和輸出的狀況,也就是它的真值表,對他內部的構成是不關心的。markdown

因此一個電子元件對硬件組裝師而言,就是一個有引腳的黑塊,一個可以完成特定功能的邏輯單元。類比一下三方類庫,在引入以後,不須要知道庫的具體實現邏輯,只要按照暴露的API(真值表),你進行一個API的調用(輸入),就會完成特定的功能(輸出)。ide


類也是這樣,最終目的是要實現邏輯的複用,讓其成爲一個可獨立存在的邏輯單元,從而和其餘類共同運做。可是如今與上面所不一樣的是:咱們不僅僅是一個使用者,更多角色的是它的設計者。因此須要考慮的要點不只是此類和其餘類如何契合運做,更重要的是它的內部構成。函數

要知道,使用一個電子元件,和設計製造一個電子元件是徹底不一樣的,而程序設計師顯然是後者。post


1.1.2:繼承的思想

一我的的出生並不是是一無全部,它享有着父母的資產,人脈,地位。這些都是他可使用的資源。 這就無需艱苦奮鬥來達到當前的境地,從而可以在將來的發展中更上一層。固然,類也是這樣,子類經過繼承能夠享受到父類所帶來的'天賦'。測試

這就涉及到了一個概念,叫抽象。抽象並不是隨便抽的,不以解決問題爲前提的抽象都是耍流氓,經過抽象來提取對象的公共特徵,造成基類。這至關因而父母在努力打拼,想爲孩子打造一個適宜的生存環境。flex

還有一點就是:孩子不必定繼承父親的一切,有些東西是父親不想給孩子的,這就涉及到繼承的訪問限制。ui


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中類的建立,屬性,方法的書寫以及類的繼承有所理解了吧。


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的頭,就用這個類來講明一下:

---->[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中的其餘語法。

最後說一句:祝你們早日可以真正的面向你的對象來編程👻

相關文章
相關標籤/搜索