Dart 基礎 - 3

Dart Basic - 3

Posted by Bobson Lin on Thursday, March 21, 2019

運算子(Operators)

四則運算、邏輯運算、程式運算等比較基礎的運算部份請參看官方文檔。
在此講一些比較特別的運算子

as, is, is!

// Type test operators

num n = 1.1;
print(n is! int);
print(n is double);
true
true

條件算符(Conditional expressions)

大部分程式語言都會有三元運算子 ?:,它可以很簡短的代替 if-else 語法。 Dart 中有幾個比較有趣的寫法 ???. 等。

???. 都會判斷左方的運算元是否為 null,再進行動作。

此範例若有些看不懂,可以看完下面一節 類別(上) 後再回頭看一次這裡。

class Account {
  String username;
  String password;

  Account({username, password})
    : this.username = username,
      // 下面兩種寫法是等價的

      // this.password = (password != null) ? password : 'AutomaticallyGenerateSecretPassword';

      this.password = password ?? 'AutomaticallyGenerateSecretPassword';

  @override
  String toString() {
    return "Account($username, $password)";
  }
}
var a = Account(username: 'Bobson');
print(a);

var b = Account();
b?.username = "I'm Groot";
print(b);
Account(Bobson, AutomaticallyGenerateSecretPassword)
Account(I'm Groot, AutomaticallyGenerateSecretPassword)

級聯符號(Cascade notation)

級聯符號可讓你針對一個物件進行一連串的函數操作,可省下寫重複變數的動作。

不用級聯符號的寫法:

var list = List()
list.insert(0, 123)
list.add(456)
list.addAll([789, 111]);

用級聯符號的寫法:

var list = List()
  ..insert(0, 123)
  ..add(456)
  ..addAll([789, 111]);
print(list);
[123, 456, 789, 111]

類別(Class) - 上篇

物件導向程式(OOP),是軟體程式設計中很重要的一環,打算用較長篇幅講解。

物件導向簡單說,從大到小;從整體應用(業務)邏輯到程式組件,都為他們繪製藍圖(也就是類別),
而電腦在執行程式時,會依照藍圖製造出成品(也就是物件實例),中間的製造過程統稱實例化

直接來看些 Dart 中 OOP 的應用吧。

// Point Class from dart:math

var p1 = Point(1, 2);
var p2 = Point(3, 4);
print("p1: $p1, p2: $p2");
print(p1.distanceTo(p2));
print(p2.magnitude);
p1: Point(1, 2), p2: Point(3, 4)
2.8284271247461903
5.0

Dart 中的 OOP 與其他 OOP 語言特性並無太多不同,
我找了 Dart 的數學函式庫 dart:math 中的 Point (二維平面上的點) 作為開篇。

class Point<T extends num> {
  final T x;
  final T y;

  const Point(T x, T y)
      : this.x = x,
        this.y = y;

  String toString() => 'Point($x, $y)';

  ...

  /**
   * Get the straight line (Euclidean) distance between the origin (0, 0) and
   * this point.
   */
  double get magnitude => sqrt(x * x + y * y);

  /**
   * Returns the distance between `this` and [other].
   */
  double distanceTo(Point<T> other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return sqrt(dx * dx + dy * dy);
  }

  ...
}

類別通常都會包含幾個東西:

  • 屬性(property) 或 成員(member) - 物件會用到的變數
  • 建構子(constructor) - 可理解成創建(初始化)物件時會調用的函數
  • 方法(methods) - 物件會用到的函數

Point 對應來說:

  • 屬性或成員 -
    • final T x (此點的 x 座標)
    • final T y (此點的 y 座標)
  • 建構子 - const Point(T x, T y)...
  • 方法 -
    • String toString()... (print 時所顯示的字串),
    • double get magnitude... (此點與原點的距離),
    • double distanceTo(Point<T> other)... (此點與另一點的距離)

對應完之後是不是覺得很好理解了呢,這也是 OOP 的迷人之處。

接下來,我們試看看用 Dart 實現數學中複數(complex)的類別吧。

Complex Number (複數) - x + yi (i = √-1) 其中 x 及 y 皆為實數,分別稱為複數之「實部」和「虛部」。

class BaseComplex {
  num real;
  num imaginary;

  @override
  bool operator ==(dynamic other) {
    if (other is! BaseComplex) {
      return false;
    } else {
      return real == other.real && imaginary == other.imaginary;
    }
  }

  @override
  String toString() => "BaseComplex($real, $imaginary)";
}
  • 屬性或成員 -
    • num real (實部)
    • num imaginary (虛部)
  • 建構子 - 無
  • 方法 -
    • bool operator ==(dynamic other)... (複數物件相等函數)
    • String toString()... (print 時所顯示的字串),

稍稍分解一下,有兩項額外重點:

  • 建構子不寫的話會有一個預設的建構子可以調用,別忘了 Dart 中一次都繼承於 Object,所以實際上預設是調用了 Object 的建構子。
  • @override 這裝飾子是用來覆寫父類別的方法,Object 中包含了 ==()toString()

接著實際建造 BaseComplex 看看吧~

var c1 = BaseComplex();
c1.real = 5;
c1.imaginary = 2;
print(c1.runtimeType);

var c2 = BaseComplex()
  ..real = 5
  ..imaginary = 2;
print("c1: $c1, c2: $c2");

print(c1 == c2);
BaseComplex
c1: BaseComplex(5, 2), c2: BaseComplex(5, 2)
true
  • runtimeType 可顯示程式執行時,此變數的型別是什麼
  • print("c1: $c1, c2: $c2"); print 時會調用到物件的 toString 函數,若無覆寫 toString會顯示 Instance of 'BaseComplex'
  • c1 == c2 中實際上會調用我們覆寫的 == 函數。

稍微有些基礎建立類別方法了吧~
要多加一些特性在實際 Complex 物件囉。

class Complex {
  // Private property(member)

  num _real;
  num _imaginary;

  // Getters & Setters

  get real => _real;
  set real(num newReal) => _real = newReal;

  get imaginary => _imaginary;
  set imaginary(num newImaginary) => _imaginary = newImaginary;

  // Traditional way

  // Complex(num real, num imaginary) { 
 
  //   this.real = real;  

  //   this.imaginary = imaginary;  

  // }


  // Syntactic sugar

  Complex(this._real, this._imaginary);

  // Named Constructor

  Complex.real(num real) : this(real, 0);
  Complex.imaginary(num imaginary) : this(0, imaginary);

  @override
  bool operator ==(dynamic other) {
    if (other is! Complex) {
      return false;
    } else {
      return real == other.real && imaginary == other.imaginary;
    }
  }

  @override
  String toString() {
    if (imaginary >= 0) {
      return "$real + ${imaginary}i";
    } else {
      return "$real - ${imaginary.abs()}i";
    }
  }
}

這裡整理一下,加了哪些東西?

  • Private property(member) - (相當於 Java 裡的 private)
    • Dart 中的類別屬性(成員)前面加 _,可達到屬性被保護的作用。
    • 建造出來的物件 private 屬性無法直接透過 . 獲取。
  • Getters & Setters - (相當於 JavaBeans)
    • 屬於類別中的方法(methods),想要類別中的屬性有讀與寫的權限,可以讓屬性變 private,並寫個自定義的 Get 函數(讀)、Set 函數(寫)。
    • 但在物件操作上如此(都用自定義Get 函數和 Set 函數)會變得不方便,所以可直接使用 getset,在 Dart 中針對他們有對應的 keyword 可用。
  • 建構子
    • 傳統一般的建構子會用大括號來定義其初始的內容。
    • 在 Dart 中有更精簡的寫法(Syntactic sugar 語法糖)。
  • 命名建構子(Named Constructor)
    • 有時類別內會有特別常用的特定型態,可以額外寫成命名建構子。
    • 在 Flutter 中可以常常看到這種應用,如 ListView 中就有好幾個命名建構子 ListView.builderListView.seperatedListView.custom
  • 初始化列表(Initializer list)
    • 在建構子建構物件之前,可以用 : 來設定一些初始值。
    • 在 Flutter 源碼中可以常常看到這種應用。

你也可以瞧瞧…

參考

系列文