(五)Flutter學習之Dart面向對象

前言

Dart 是一門面向對象的語言, 全部的類都是繼承自 Object , 除了支持傳統的 繼承、封裝、多態, 還有基於組合(Mixin-based)的繼承特性, 這個特性也是本文將會重點介紹的地方json

既然是面向對象的語言, 先讓咱們來看下如何定義一個類:緩存

class ClassName{
}

複製代碼

經過 class 關鍵字定義類, 語法和其餘主流的語言沒有什麼差異bash

有過使用面嚮對象語言經驗的開發者知道, 通常類由成員變量, 成員方法, 構造方法, 靜態變量, 靜態方法組成ide

成員變量和靜態變量

下面咱們來看下如何聲明成員變量:函數

class Point {
  // 聲明成員變量 x
  num x;
  // 聲明成員變量 y
  num y;
}
複製代碼

成員變量默認會生成一個隱式的 getter 方法, 而且 非final 的成員變量也會隱式的生成 setter 方法post

除此之外, 開發者還可使用 getset 關鍵字實現 gettersetter 方法來建立新的屬性, 如:學習

class Rectangle {

  num left, top, width, height;
  
  // 構造函數
  Rectangle(this.left, this.top, this.width, this.height);

  // 定義兩個屬性: right and bottom.
  
  num get right => left + width;
  set right(num value) => left = value - width;
  
  num get bottom => top + height;
  set bottom(num value) => top = value - height;
}
複製代碼

聲明靜態變量, 在前面聲明成員變量的基礎上加上 static 關鍵字:測試

class Person{
  // 靜態變量
  static var count = 0;
  
  // 靜態常量
  //static const var count = 0;
}
複製代碼

成員方法和靜態方法

若是定義函數已經在 (三)Flutter學習之Dart函數 介紹過了ui

類的成員方法就是把之前介紹的方法定義到類裏面而已this

class Person {
  void sayHello() {
    print("hello...");
  }
}

void main() {
  var p = Person();
  p.sayHello();
}
複製代碼

類的靜態方法, 就是在成員方法基礎上加上 static 關鍵字, 訪問的時候只須要類名便可而不須要對象, 這個和 Java 是同樣的

class Person {
  static void printDesc() {
    print("this is person description...");
  }
}

void main() {
  Person.printDesc();
}

複製代碼

構造方法

在類中建立一個和類名同樣的函數這就是構造函數, 如:

class Point {
  // 成員變量
  num x, y;

  // 構造方法
  Point(num x, num y) {
    this.x = x;
    this.y = y;
  }
}

複製代碼

Dart 爲咱們提供了語法糖來簡化上面的代碼:

class Point {
  num x, y;
  
  // 聲明構造方法, 接受兩個參數, 而且將參數分別賦值給對應的變量上
  Point(this.x, this.y);
}
複製代碼

Dart 還爲咱們提供了 Named constructor, 讓代碼可讀性更高, 讓開發者經過名字就知道該構造方法是幹什麼的

class Person {
  String firstName;

  // 聲明 Named constructor
  Person.fromJson(Map data) {
    print('in Person');
  }
}

void main() {
  var p = Person.fromJson({});
}
複製代碼

若是想將構造方法聲明爲 private 的, 加上下劃線便可:

// 私有構造函數, 只能在當前類可用
Person._fromJson(Map data) {
  print('in Person');
}
複製代碼

構造方法還能夠調用另外一個構造方法:

class Point {
  num x, y;

  Point(this.x, this.y);

  // 經過 this 關鍵字調用其餘構造函數
  Point.alongXAxis(num x) : this(x, 0);
}
複製代碼

除了在構造方法中調用其餘的構造方法, 還能夠調用父類的構造方法:

class Employee extends Person {
  // 經過 super 關鍵字調用父類的構造方法
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}
複製代碼

初始化器列表(Initializer list)

Dart 還支持 初始化器列表(Initializer list), 它在構造方法體執行以前執行, 在構造方法後用 冒號(:) 分隔初始化器列表, 例如:

Point.fromJson(Map<String, num> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x, $y)');
}

複製代碼

因爲初始化器列表初始化成員變量操做是在構造方法體執行前執行, 因此初始化器列表中不能使用 this 關鍵字

在開發期間還能夠在初始化器列表中使用斷言:

Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}
複製代碼

若是某個類建立對象後再也不發生更改, 能夠將對象聲明爲運行時常量, 將對象聲明爲常量, 須要將類的構造方法聲明爲常量構造方法

而且全部的成員變量都必須是 final

class ImmutablePoint {
  final num x, y;

  const ImmutablePoint(this.x, this.y);
}
複製代碼

若是建立兩個常量對象, 實際上他們是一個對象實例:

var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
// They are the same instance!
assert(identical(a, b)); 
複製代碼

工廠構造器(Factory Contructor)

工廠構造器就構造方法前面使用 factory 關鍵字修飾:

factory Person(String name){
}
複製代碼

其實這不是嚴格意義上的構造函數, 在其方法體內不能訪問 this, 只是爲了調用該方法的時候就像調用普通的構造方法同樣

而不用關心究竟是返回了一個新的對象仍是緩存的對象. 例以下面一個單例模式的代碼:

class Singleton {
  static final Singleton _singleton = new Singleton._internal();

  factory Singleton() {
    return _singleton;
  }

  // 將默認的構造函數聲明爲private
  Singleton._internal();
}

main() {
  // 就像調用普通的構造方法同樣
  var s1 = Singleton();
  var s2 = Singleton();
  print(identical(s1, s2));  // true
  print(s1 == s2);           // true
}
複製代碼

抽象類(Abstract classes)

抽象類使用 abstract 修飾符來修飾, 抽象類不能被實例化, 若是抽象類裏的方法沒有方法體, 那麼表示該方法是抽象方法:

abstract class AbstractContainer {
  // 抽象方法
  void updateChildren();
  
  List getChildren(){
      return container;
  }
}

複製代碼

接口(interfaces)

定義接口並無特殊的關鍵字, 使用 class 關鍵字來定義接口, 只不過裏面的方法都是沒有方法體的抽象方法

// A person. The implicit interface contains greet().
class Person {
  // In the interface, but visible only in this library.
  final _name;

  // Not in the interface, since this is a constructor.
  Person(this._name);

  // In the interface.
  String greet(String who) => 'Hello, $who. I am $_name.';
}
複製代碼

實現接口的時候須要使用 implements 關鍵字:

class Point implements Comparable, Location {...}
複製代碼

類的繼承

不論是繼承抽象類仍是普通類, 使用 extends 關鍵字

若是重載父類的方法時, 須要調用父類的方法, 使用 super 關鍵字

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
}

class SmartTelevision extends Television {
  @override
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
}
複製代碼

extends VS implements

經過上面介紹咱們知道繼承一個類使用 extends 關鍵字, 實現一個接口使用 implements 關鍵字

若是使用 implements 關鍵字實現一個非接口的類, 如:

class A {}

class B implements A{}
複製代碼

Dart 中這是容許的, 在 Javaimplements 關鍵後面只能放接口

若是 implements 一個非接口類, 須要實現該類的裏面的全部方法:

class A {
  void sayHello(){
    print("My name is A");
  }
}
class B implements A{
  @override
  void sayHello() {
    print("My name is B");
  }
}
複製代碼

mixin 特性

Dart 中的 mixin 特性主要是用來多個層級類的代碼重用

Dart 和 Java 同樣都是單繼承的,也就是說若是要複用多個類的代碼,經過繼承的方式是行不通的

在 Dart 中經過 with 關鍵字來實現 mixin 特性:

class Fly{
  void fly(){
    print("flying");
  }
}

class Animal{
}

class Bird extends Animal with Fly{
}

main(){
  Bird().fly();
}
複製代碼

從中能夠看出,咱們能夠經過非繼承的方式來使用 Fly 類的功能

Bird 除了具備 fly 的功能,還有 run 的功能, 咱們經過 mixin 多個類來實現:

class Run {
  void run() {
    print("running");
  }
}

class Bird extends Animal with Fly, Run {}

main() {
  var bird = Bird();
  bird.fly();
  bird.run();
}
複製代碼

可以被 mixin 的類須要知足必定的條件:

  • 不能有包含默認構造函數
  • 只能繼承自 Object

上面的 Bird 繼承了 Animal , 而後 mixin Fly 這個類, 若是 Animal 中也有 fly 方法它會選擇執行哪一個 fly 方法呢

class Animal {
  void fly() {
    print("Animal flying");
  }
}
複製代碼

通過測試發現會使用 Fly 類裏的 fly 方法, 也就是說 mixin Class 比 extends Class 優先級要高

若是咱們 mixin 多個類, 在這多個類中包含了相同的方法, Dart會選擇執行哪一個呢?

class FlyEnhance{
  void fly() {
    print("fly enhance");
  }
}

class Bird extends Animal with Run, Fly, FlyEnhance {}

main() {
  var bird = Bird();
  bird.fly();
  bird.run();
}

//輸出結果:
fly enhance
running
複製代碼

Fly, FlayEnhance 中都有 fly 方法, 經測試發現, 使用哪一個類的 fly 方法取決於 with 後面順序, 後面的 FlyEnhance 會覆蓋前面的 Fly 中的 fly 方法

mixin 多個類的原理分析

上面的代碼:

class Bird extends Animal with Run, Fly, FlyEnhance {}
複製代碼

底層會建立多個類來實現這個功能, 至關於下面的代碼:

class $AnimalRun Animal with Run
class $AnimalRunFly extends $AnimalRun with Fly
class $AnimalRunFlyFlyEnhance extends $AnimalRunFly with FlyEnhance

class Bird extends $AnimalRunFlyFlyEnhance
複製代碼

因此上面的代碼會額外建立三個類, 因爲 mixin 優先級高於 extends , 因此:

// 使用 Fly 裏的 fly 方法 
class $AnimalRunFly extends $AnimalRun with Fly  

// 使用 FlyEnhance 裏的 fly 方法
class $AnimalRunFlyFlyEnhance extends $AnimalRunFly with FlyEnhance
複製代碼

這就是 Dart 爲何最終會選擇使用 FlyEnhance 而不是 Fly 裏的 fly() 方法

若是一個類使用 mixin 特性, 那麼這個類的實例也是 mixin 類的子類型:

print(bird is Animal);
print(bird is Run);
print(bird is Fly);
print(bird is FlyEnhance);

//true
複製代碼

若是一個類只用於被 mixin, 能夠在聲明類的時候使用 mixin 關鍵字代替 class 關鍵字

這個類則不能被繼承(extends), 但能夠被實現(implements)

還能夠經過 on 關鍵字來指定 mixin 類的須要的父類

class SuperA{
}

mixin A on SuperA{
}

複製代碼

也就是說類 A, 對 SuperA 有依賴, 那麼其餘類在 withA 的時候, 必須繼承或者實現 SuperA

class B extends SuperA with A{ 
}
複製代碼

從中咱們能夠看出 mixin 機制對複用代碼提供了很是大的便利和靈活性

關於 Dart 面向對象就先分析到這裏

Reference

dart.dev/guides/lang…

stackoverflow.com/questions/1…

stackoverflow.com/questions/4…


聯繫我

下面是個人公衆號,乾貨文章不錯過,有須要的能夠關注下,有任何問題能夠聯繫我:

公衆號:  chiclaim
相關文章
相關標籤/搜索