Dart 基礎 - 4

Dart Basic - 4

Posted by Bobson Lin on Tuesday, March 26, 2019

類別(Class) - 中篇

延續上一節的 類別(Class) - 上篇

方法(Methods)

Dart 中有幾種類別中的方法:

  • 實例方法(Instance methods) - 就是一般類別中標準的函數,使用的時候要先以類別實例化出物件,該物件才能使用此方法。
  • 靜態方法(Static methods) - 跟一般類別中的函數很像,不過它不用實例化便可使用該方法,通常是與該類別相關所以會定義在類別裡。
  • 抽象方法(Abstract methods) - 抽象類別中的函數,用來定義樣板(或稱界面[interface]),繼承該抽象類別後所需實現(implement)的函數。
  • Getters & Setters - 範例中 Complexrealimaginary
  • 運算子方法(Operator methods) - Dart 中可以 Override 幾個常用的運算子的方法,如 + - * / > < ==… 等等。

!= 是不可複寫的,因為它相當於 !(a == b)

我們在 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;

  // Constructors

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

  Complex multiply(Complex c) {
    return Complex(
      (this.real * c.real) - (this.imaginary * c.imaginary),
      (this.real * c.imaginary) + (this.imaginary * c.real),
    );
  }

  static Complex substract(Complex c1, Complex c2) {
    return Complex(
      c1.real - c2.real,
      c1.imaginary - c2.imaginary,
    );
  }

  Complex operator +(Complex c) {
    return Complex(this.real + c.real, this.imaginary + c.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";
    }
  }
}
  • 實例方法(Instance methods) - Complex.multiply
  • 靜態方法(Static methods) - Complex.substract
  • 運算子方法(Operator methods) - Complex.+

按照邏輯,數學上的運算覆寫 運算子方法(Operator methods) 會比較合理。
這邊展示用所以才拆開;而 抽象方法(Abstract methods) 使用情況較特別,後面會在講述。

使用看看這些方法吧

var n1 = Complex(3, -4);
var n2 = Complex(5, 2);

print(n1 + n2);
print(n1.multiply(n2));
print(Complex.substract(n1, n2));
8 - 2i
23 - 14i
-2 - 6i

final & const

finalconst 變數在 Dart 中是常見的,在大部份的情況下他們的結果是相同的,就是不能任意更改賦予此變數的值。

但實際意義上是有些不同的,final 是指第一次賦予此變數值之後就不能作改變了,也是就 final 的作用是發生於 Run-time 的,也就是程式執行當下決定; 而 const 是在 Compile-time 就決定的,所以在程式編譯後就已經決定好了,程式執行當下也不能作改變。

在物件導向(OO)的類別中,常用 final 在類別的屬性,目的在於創建物件(實例)後希望該屬性不再改變,比如在 Complex 得屬性中加上 final

但後續 realimaginary Setter 方法就都不能使用,會出現 '_real' can't be used as a setter because it is final. 的錯誤。

class Complex {
  // Private property(member)
  final num _real;
  final num _imaginary;

  get real => _real;
  set real(num newReal) => _real = newReal; // 會出現 Error.


  get imaginary => _imaginary;
  set imaginary(num newImaginary) => _imaginary = newImaginary; // 會出現 Error.

  ...
}

const 在物件中,需寫成 static const 通常都是常用且不變的類別屬性,比如 dart:math 中的數學常數、package:http/http.dart 中的 HttpHeaders

// dart:math

/**
 * Base of the natural logarithms.
 *
 * Typically written as "e".
 */
const double e = 2.718281828459045;

...

/**
 * The PI constant.
 */
const double pi = 3.1415926535897932;

...
// package:http/http.dart

abstract class HttpHeaders {
  static const acceptHeader = "accept";
  static const acceptCharsetHeader = "accept-charset";
  static const acceptEncodingHeader = "accept-encoding";
  static const acceptLanguageHeader = "accept-language";
  static const acceptRangesHeader = "accept-ranges";
  static const ageHeader = "age";
  static const allowHeader = "allow";
  static const authorizationHeader = "authorization";
  
  ...
}

繼承(Inheritance) - 擴展(extends)

物件導向(Object-Oriented)中的繼承,是一個非常核心的概念,可細分非常多種。
要理解很多種概念(或是設計模式),才有辦法做出更好程式設計。

這邊主要講的是 extends (擴展、延伸)。

標準的寫法 class 子類 extends 父類(基類)
這樣的用法主要是子類必須包含父類的所有特性,並且有自己的額外特性。此用法可以達成程式碼重用(reuse)效果。

注意: Dart 中的 extends 只支援單一繼承。

Dart 簡單一點的繼承應用,可以看到 numintdouble 的繼承關係。

abstract class double extends num {
  static const double nan = 0.0 / 0.0;
  static const double infinity = 1.0 / 0.0;
  static const double negativeInfinity = -infinity;
  static const double minPositive = 5e-324;
  static const double maxFinite = 1.7976931348623157e+308;   

  double remainder(num other);

  ...
}
abstract class int extends num {  

  ...

  int gcd(int other);

  /** Returns true if and only if this integer is even. */
  bool get isEven;

  /** Returns true if and only if this integer is odd. */
  bool get isOdd;

  ...
}
  • 除了 num 的基本特性,intdouble 多了自己的特性。

複雜一點,可以看 Flutter 中的 ListViewBoxScrollView 的繼承關係。

class ListView extends BoxScrollView {
  ListView({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    this.itemExtent,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double cacheExtent,
    List<Widget> children = const <Widget>[],
    int semanticChildCount,
  }) : childrenDelegate = SliverChildListDelegate(
         children,
         addAutomaticKeepAlives: addAutomaticKeepAlives,
         addRepaintBoundaries: addRepaintBoundaries,
         addSemanticIndexes: addSemanticIndexes,
       ), super(
    key: key,
    scrollDirection: scrollDirection,
    reverse: reverse,
    controller: controller,
    primary: primary,
    physics: physics,
    shrinkWrap: shrinkWrap,
    padding: padding,
    cacheExtent: cacheExtent,
    semanticChildCount: semanticChildCount ?? children.length,
  );

  ...

  final double itemExtent;

  final SliverChildDelegate childrenDelegate;

  @override
  Widget buildChildLayout(BuildContext context) {
    if (itemExtent != null) {
      return SliverFixedExtentList(
        delegate: childrenDelegate,
        itemExtent: itemExtent,
      );
    }
    return SliverList(delegate: childrenDelegate);
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DoubleProperty('itemExtent', itemExtent, defaultValue: null));
  }

  // Helper method to compute the semantic child count for the separated constructor.
  static int _computeSemanticChildCount(int itemCount) {
    return math.max(0, itemCount * 2 - 1);
  }
}
  • ListView 的建構子中的初始列表,把相當多的特性都用 super 傳給了父類 BoxScrollView
  • ListView 擁有自己的特性,如屬性 itemExtentchildrenDelegate;方法 _computeSemanticChildCount
  • ListView 也覆寫了父類的特性,如 buildChildLayoutdebugFillProperties

物件導向中的繼承是個概念簡單,但實際上使用可以相當複雜。
需要多看多寫程式碼才能更加深入了解這概念的博大精深。


重構也是程序員常做事情,畢竟人也是會慢慢進步,會發現以前寫的程式碼的一些缺陷,進而改進。

你也可以瞧瞧…

參考

系列文