Dart語言一日遊

ShadowJoker發表於2019-03-18

Dart語言一日遊

前言

Dart是谷歌Flutter框架使用的語言。Dart語言目前還是個冷門語言,但是它很好上手,對於有程式設計基礎的人來說,一天時間足夠掌握了。本文就是結合我自己的學習過程,給大家總結一下經驗,幫助大家快速上手。

學習Dart語言,這一篇文章就夠用了。

本文包括哪些知識點

  • 如何建立一個類和它的構造器
  • 如何定義類的變數
  • 如何建立getter和setter
  • Dart對於變數私有公有訪問的控制
  • 如何使用工廠模式建立一個類例項
  • 類的繼承機制
  • Dart函數語言程式設計

需要準備的東西

因為本文只是學習Dart語言,只需要一個能科學上網的瀏覽器就夠用了。

Dart提供了線上的IDE開發環境DartPad,極大的方便了我們的學習。

萬物之源:類和物件

對於一門物件導向的程式語言,類和物件永遠都是最重要的組成部分。所以我們首先來看如何建立一個Dart類。

開啟DartPad,發現已經幫你寫好了一個HelloWorld程式了。其中main函式就是入口主函式。這個函式迴圈輸出四行字串。

void main() {
  for (var i = 0; i < 4; i++) {
    print('hello $i');
  }
}
複製程式碼

類的建立

下面我們來建立一個類,我們用自行車來距離。建立一個Bicycle類。程式碼如下:

class Bicycle{
  int speed;
  String desc;
}

void main() {
}
複製程式碼

怎麼樣,是不是非常熟悉?(注意,這裡每行結尾還是要寫分號,略蛋疼)

這個類有兩個屬性,速度和描述。如何建立一個例項呢?也很簡單,這樣就好了。

class Bicycle{
  int speed;
  String desc;
}

void main() {
  var bike = Bicycle();
  bike.speed = 20;
  bike.desc = "我的自行車";
  print(bike);
}
複製程式碼

點一下頁面上的run按鈕,輸出結果:

Instance of 'Bicycle'
複製程式碼

這裡有兩個知識點:

  1. 我們用var關鍵字建立了一個bike例項並且輸出。如果想要建立一個不可變的變數,用關鍵字final。(這個final就是kotlin的val)
  2. 類的屬性都沒有public或者private關鍵字。沒錯,Dart沒有publicprivate關鍵字,類裡面宣告的屬性預設都是公共的。這個要注意一下。如何宣告私有變數後面再講。

建構函式

能不能像我們習慣的那樣寫個建構函式傳參呢?當然可以,這樣寫就行了:

class Bicycle {
  int speed;
  String desc;

  Bicycle(this.speed, this.desc);
}

void main() {
  var bike = Bicycle(20, "我的自行車");
  print(bike);
}

// 上面的簡便建構函式寫法與下面的等價:
Bicycle(int speed, String desc) {
  this.speed = speed;
  this.desc = desc;
}
複製程式碼

run一下,輸出和剛才還是一樣的

Instance of 'Bicycle'
複製程式碼

這個輸出不太友好,我們來改進一下。同Java一樣,每個類有自己的toString()函式,我們覆寫它就可以。

class Bicycle {
  int speed;
  String desc;

  Bicycle(this.speed, this.desc);

  @override
  String toString() {
    return '$desc速度: $speed 公里/小時';
  }
  
  // 簡便寫法
  @override
  String toString() => '$desc速度: $speed 公里/小時';
}
複製程式碼

注意,函式前面不用寫fun關鍵字。簡便的寫法中,用=>符號來確定返回值。最後的輸出為:

我的自行車速度: 20 公里/小時
複製程式碼

好了下面我們來介紹一下如何宣告一個私有的變數。因為沒有private關鍵字,所以Dart用了一個比較蛋疼的處理,就是變數名前面加下劃線,你沒看錯,是下劃線。。。所以我們把desc變數宣告為_desc,這樣就把它私有化了。

class Bicycle {
  int speed;
  String _desc;

  Bicycle(int speed) {
    this.speed = speed;
    this._desc = "我的自行車";
  }

  @override
  String toString() {
    return '$_desc速度: $speed 公里/小時';
  }
}

void main() {
  var bike = Bicycle(20);
  print(bike);
}
複製程式碼

這樣修改後,在main函式中就無法訪問desc變數了,不能set也不能get。

那麼問題又來了,如果我想建立一個只讀的變數呢?能get但是不能set。這個就需要在私有變數的基礎上,自己提供一個get函式了。寫法是這樣的

class Bicycle {
  int speed;
  String _desc;

  // 為私有變數提供get函式
  String get desc => _desc;

  Bicycle(int speed) {
    this.speed = speed;
    this._desc = "我的自行車";
  }

  @override
  String toString() {
    return '$_desc速度: $speed 公里/小時';
  }
}

void main() {
  var bike = Bicycle(20);
  print(bike.desc);
  print(bike);
}
複製程式碼

最後介紹一下為一個類新增函式,這個非常簡單,看一下就好:

class Bicycle {
  int speed;
  String _desc;
  
  void speedUp(int value) {
    this.speed += value;
  }

  int getSpeed() {
    return this.speed;
  }
}
複製程式碼

OK,類和物件這個部分已經完成了。

可選引數

Dart和Kotlin一樣,支援函式設定可選引數,避免Java那種累贅的過載。這裡我們用這個例子RectangleExample

為這個類加入一個可選引數的建構函式,最後再加一個toSting。

class Rectangle {
  int width;
  int height;
  Point origin;

  Rectangle({Point origin = const Point(0, 0), int width, int height}) {
    this.origin = origin;
    this.width = width;
    this.height = height;
  }
  
  @override
  String toString() =>
      'Origin: (${origin.x}, ${origin.y}), width: $width, height: $height';
}
複製程式碼

這裡座標orgin設定了預設引數。注意,使用預設引數時,需要將引數包裹在{ }中。之後再main函式裡進行呼叫:

void main() {
  print(Rectangle(origin: const Point(10, 20), width: 100, height: 200));
  print(Rectangle(origin: const Point(10, 10)));
  print(Rectangle(width: 200));
  print(Rectangle());
  
  // 注意:這樣寫會報錯,需要加引數名
  print(Rectangle(100, 100));
}
複製程式碼

注意,這裡需要填寫引數名稱,不能直接給值。這一點上相比於Kotlin確實要麻煩一些。

最後控制檯的輸出是這樣的:

Origin: (10, 20), width: 100, height: 200
Origin: (10, 10), width: null, height: null
Origin: (0, 0), width: 200, height: null
Origin: (0, 0), width: null, height: null
複製程式碼

使用工廠模式

使用工廠模式建立類的例項是開發中常用的情況。Dart對此還特意提供了一個factory關鍵字。下面我們來介紹一下如何在Dart中快速實現工廠模式。先看這個例子:

import 'dart:math';

abstract class Shape {
  num get area;
}

class Circle implements Shape {
  final num radius;
  Circle(this.radius);
  num get area => pi * pow(radius, 2);
}

class Square implements Shape {
  final num side;
  Square(this.side);
  num get area => pow(side, 2);
}
複製程式碼

這裡有幾個知識點:

  • 第一行的import,說明Dart提供了數學運算等一系列基礎庫,例如 dart:core,dart:async,dart:convert,dart:collection,這個後面可以自己去看API詳細瞭解
  • 和Kotlin一樣,一個檔案可以定義多個類
  • Dart也是支援抽象類的,繼承的時候,使用implements關鍵字。Dart中沒有interface介面的概念,這個後面再細講。

下面針對父類Shape加入一個工廠建構函式,使用關鍵字factory,並且在main函式中呼叫,程式碼如下:

abstract class Shape {
  num get area;
  factory Shape(String type) {
    if (type == 'circle') return Circle(2);
    if (type == 'square') return Square(2);
    throw 'Can\'t create $type.';
  }
}

void main() {
  try {
    print(Shape('circle').area);
    print(Shape('square').area);
    print(Shape('triangle').area);
  } catch (err) {
    print(err);
  }
}
複製程式碼

輸出如下:

12.566370614359172
4
Can't create triangle.
複製程式碼

觀察這段程式碼,裡面引入了異常處理機制。使用工廠函式建立的時候,如果傳入的型別不合法,會throw一個異常。然後在main函式中使用try/catch函式來捕獲這個異常並處理。另外,因為示例程式碼中用單引號包裹字串,所以異常文案中的Can't creat用到了轉義字元,用雙引號包裹字串也是可以的。

當然除了上面介紹的使用factory關鍵字建立工廠建構函式之外,你也可以像Kotlin一樣,自己寫一個工廠函式,例如:

Shape shapeFactory(String type) {
  if (type == 'circle') return Circle(2);
  if (type == 'square') return Square(2);
  throw 'Can\'t create $type.';
}
複製程式碼

這個效果是一樣的,覺得那種順手就用哪一種吧。 示例程式碼在這裡

Interface介面問題

前面說了,Dart沒有Java裡面Interface的概念。為什麼呢?我來告訴你為什麼?你說為什麼?!

額,抱歉,寫了這長了,偶爾皮一下。

實際上是因為每一個Dart類都隱形的有一個介面。到底什麼意思呢?來看看剛才的例子

在其中加入一個CircleImpl類繼承Circle:

class CircleImpl implements Circle {}
複製程式碼

執行發現會報錯:

Error: The non-abstract class 'CircleImpl' is missing implementations for these members:
 - 'radius'
 - 'area'
複製程式碼

這是因為Circle中含有radius和area兩個屬性,雖然Circle類中已經實現了這兩個引數的處理,但是CircleImpl類並沒有繼承到具體實現。

所以Dart的繼承並不是真正意義上的繼承那個類,而是繼承這個類對應的資料結構的介面,這個資料結構包括它的引數和函式,但是不包括實現。這也是為什麼Dart繼承的關鍵字是implement而不是extends

CircleImpl類繼承的不是Circle,只是繼承了它的資料結構對應的介面,只是告訴你需要有radius和area兩個屬性,但是沒有把實現繼承給你。

所以我們把CircleImpl類改成這樣就好了:

class CircleImpl implements Circle {
  num radius;
  num area;
}
複製程式碼

可以自己嘗試寫一個帶函式的類,然後寫個子類繼承它,看看是什麼效果?

函數語言程式設計

函數語言程式設計對於熟練使用Kotlin和Swift的人應該都不陌生了,Dart也是有相應的特性的。

下面我們來介紹一下,首先是函式的返回可以直接是一個表示式,這個前面都見過:

// 函式式風格寫法
String sayHello() => "Hello World";

// 等價於傳統返回值寫法
String sayHello() {
  return "Hello World";
}
複製程式碼

再來看看對映操作、集合遍歷和將函式作為引數的特性:

String sayHello(int time) => "Hello World! $time";

main() {
  final values = [1, 2, 3, 4, 5, 6];
  values.map(sayHello).forEach(print);
}

// 輸出結果
Hello World! 1
Hello World! 2
Hello World! 3
Hello World! 4
Hello World! 5
Hello World! 6
複製程式碼

這裡首先用到了map對映,values是一個int陣列,用map對映,將int通過函式sayHello變成了一個字串陣列。之後再用集合遍歷forEach,遍歷的呼叫print函式列印結果。這裡map和forEach都是將一個函式當做引數接收了。

此外Dart集合框架中支援map和set資料結構,而且提供了類似Kotlin的那些便捷函式,例如下面這個寫法,就是跳過第一個元素,之後再取前三個元素進行處理:

main() {
  final values = [1, 2, 3, 4, 5, 6];
  values.skip(1).take(3).map(sayHello).forEach(print);
}

// 輸出結果
Hello World! 2
Hello World! 3
Hello World! 4
複製程式碼

最後說說控制流,為什麼寫到最後還不講講控制流呢,因為Dart的控制流和Java真的一模一樣,什麼if/else啊,switch啊都一樣,除了沒有when語句。這個隨便寫寫就知道了。

寫在最後

恭喜你已經入坑了。讀到這裡你已經掌握了Dart語言的全部基礎知識!怎麼樣不難吧,雖然Dart冷門了點,但是技多不壓身嘛。

詳細的類庫API可以查閱Dart語言官網。這裡寫的非常詳細。就像當年剛學Java的時候看JavaAPI一樣使用它就行了。

後面我會再寫寫Flutter的遊玩感受。這個東西我個人認為目前就是調研調研,簡單瞭解一下就好,沒必要深入學習。其實Dart語言看完這篇文章就夠用了,太細節的類庫API用到的時候查一下就好了。

ShadowJoker,2019.03.17

相關文章