40分鐘快速入門Dart基礎(中)

mr.xu發表於2020-07-14

40分鐘快速入門Dart基礎(中)

最近時間太忙,天天加班加點,但是我們的學習的行為不能落下。

小夥伴們繼續跟著我一起來學習40分鐘快速入門Dart基礎(中)

鎮樓目錄:

40分鐘快速入門Dart基礎(中)

在本章我們主要跟大家聊聊方法和類。這兩大章看似不多,其實也不少,不過小夥伴們不用擔心,只要大家按照我下面的程式碼敲一遍,保證能掌握大部分知識點!

好廢話不多說,直接開幹。

一、一等方法物件

Dart 是一個真正的面嚮物件語言,方法也是物件並且具有一種 型別 Function。 這意味著,方法可以賦值給變數,也可以當做其他方法的引數,同時也可以把方法當做引數呼叫另外一個方法

import 'dart:core';

void main() {
  var list = ["黃藥師", "郭靖", "小龍女"];

  void printElement(element) {
    print(element);
  }
  list.forEach(printElement); 
}

複製程式碼

在Java中如果需要能夠通知呼叫者或者其他地方方法執行過程的各種情況,可能需要指定一個介面,其實就是我們說的回撥函式。比如View的onClickListener。而在Dart中,我們可以直接指定一個回撥方法給呼叫的方法,由呼叫的方法在合適的時機執行這個回撥。

void setListener(Function listener){
    listener("Success");
}
//或者
void setListener(void listener(String result)){
    listener("Success");
}

//兩種方式,第一種呼叫者根本不確定 回撥函式的返回值、引數是些什麼
//第二中則需要寫這麼一大段 太麻煩了。

//第三種:型別定義 將返回值為voide,引數為一個String的方法定義為一個型別。
typedef  void Listener(String result);
void setListener(Listener listener){
  listener("Success");
}

複製程式碼

在Dart 中方法可以有兩種型別的引數:必需的和可選的。 必需的引數需要在引數列表前面, 後面再定義可選引數。

二、可選命名引數

什麼叫可選命名函式,其實說白了就是把方法的引數放到 {} 中就變成了可選命名引數。

import 'dart:core';

void main() {
  int add({int i, int j}) {
    if (i == null || j == null) {
      return 0;
    }
    return i + j;
  }

  //全參呼叫
  print("--1--${add(i: 10, j: 20)}"); //輸出:30
  //不傳引數(呼叫也是正確的)
  print("--2--${add()}"); //輸出:0
  //選擇傳遞引數
  print("--3--${add(j: 20)}"); //輸出:0
  //與位置無關
  print("--4--${add(j: 20, i: 10)}"); //輸出:30
}

複製程式碼

三、可選位置引數

什麼叫可選命名函式,其實說白了就是把方法的引數放到 [] 中就變成了可選命名引數。

void main() {
  int add([int i, int j]) {
    if (i == null || j == null) {
      return 0;
    }
    return i + j;
  }

  //全參呼叫
  print("--1--${add(10, 20)}"); //輸出:30
  //不傳引數(呼叫也是正確的)
  print("--2--${add()}"); //輸出:0
  //選擇傳遞引數
  print("--3--${add(10)}"); //輸出:0
}

複製程式碼

四、預設引數

什麼叫做預設引數,其實就是在定義方法的時候,可選引數可以使用 = 來定義可選引數的預設值。

import 'dart:core';

void main() {
  int sum([int age1 = 1, int age2 = 2])  {
    if (age1 == null || age2 == null) {
      return 0;
    }
    return age1 + age2;
  }

  //全參呼叫
  print("--1--${sum(10, 20)}"); //輸出:30
  //不傳引數(呼叫也是正確的)
  print("--2--${sum()}"); //輸出:3
  //選擇傳遞引數
  print("--3--${sum(20)}"); //輸出:22
}
複製程式碼

五、匿名函式

首先什麼叫匿名函式,簡單來說:

大多數方法都是有名字的,比如 main() 或 printElement()。你可以建立一個沒有名字的方法,稱之為 匿名函式,或Lambda表示式 或Closure閉包。你可以將匿名方法賦值給一個變數然後使用它,比如將該變數新增到集合或從中刪除。

([Type] param1, …) { 
  codeBlock; 
}; 
複製程式碼

匿名方法看起來與命名方法類似,在括號之間可以定義引數,引數之間用逗號分割。 後面大括號中的內容則為函式體:下面程式碼定義了只有一個引數 item 且沒有引數型別的匿名方法。List 中的每個元素都會呼叫這個函式,列印元素位置和值的字串:

import 'dart:core';

void main() {
  var list = ['黃藥師', '楊過', '老頑童'];
  list.forEach((item) {
    print('${list.indexOf(item)}: $item'); //輸出:0: 黃藥師 1: 楊過 2: 老頑童
  });

  // 如果函式體內只有一行語句,你可以使用箭頭語法:
  list.forEach(
          (item) => print('${list.indexOf(item)}: $item')); //輸出:0: 黃藥師 1: 楊過 2: 老頑童
}
複製程式碼

至此在開發flutter 過程中常用的方法知識點我們講完了啊

其實方法內容不止這些,接下來的小夥伴可以自行挖掘:比如方法作用域等。

接下來我們進入類講解

六、類

Dart 是一個物件導向程式語言。 每個物件都是一個類的例項,所有的類都繼承於 Object。同時也支援物件導向的特性,比如:類、介面、抽象等。

使用class關鍵字宣告一個dart類,後面跟類名,並且由一對花括號包圍的類體 所有類都有同一個基類,Object,dart的繼承機制使用了Mixin;

//虛擬碼
class class_name {
  <fields> //欄位,類中宣告任何變數、常量;
  <getters/setters>  // 如果物件為final,或const,只有一個getter方法 這個等會我們會有例項產生
  <constructors>   // 建構函式,為類的物件分配記憶體
  <functions>   //函式,也叫方法,物件的操作;
}
複製程式碼

上面我們提到 <getters/setters>方法,其實每個例項變數都會自動生成一個 getter 方法(隱含的)。 非final 例項變數還會自動生成一個 setter 方法。

class User {
  var name;
  var age;

  User(this.name, this.age);
}


void main(){
  
  var user =User("黃藥師",50);

  var _name = user.name;
  var _age = user.age;
  
  print("-----$_name$_age"); //輸出:黃藥師 50
  
}
複製程式碼

七、類--建構函式

Dart建構函式有種實現方式:

  • 預設構造方法
  • 命名構造方法Class.name(var param)
  • 呼叫父類構造方法
  • 不可變物件,定義編譯時常量物件,建構函式前加const
  • 工廠建構函式:factory

預設建構函式往往也是我們最常見的也是最簡單的如下

class User {
  var name;
  var age;

  User(this.name, this.age); //預設建構函式
}
複製程式碼

命名建構函式

Dart 並不支援建構函式的過載,而採用了命名建構函式為一個類實現多個建構函式:

class User {
  var name;
  var age;

  User(this.name, this.age); //預設建構函式
  //User(this.name); ///錯誤,因為不准許過載
  User.age(this.age) {
    name = "歐陽鋒";
  }
}

void main() {
  var user = User.age(50);
  print("----${user.name}${user.age}"); //輸出:歐陽鋒 50
}
複製程式碼

重定向建構函式

有時候一個建構函式會調動類中的其他建構函式(在Java中就是 this(...))。 一個重定向建構函式是沒有程式碼的,在建構函式宣告後,使用 冒號呼叫其他建構函式。

class User {
  var name;
  var age;

  User(this.name, this.age); //預設建構函式

  User.user(name, age) : this(name, age);
}

void main() {
  var user = User.user("黃藥師", 50);
  print("----${user.name}${user.age}"); //輸出:黃藥師50
}
複製程式碼

常量建構函式

如果你的類提供一個狀態不變的物件,你可以把這些物件 定義為編譯時常量。要實現這個功能,需要定義一個 const 建構函式, 並且宣告所有類的變數為 final。

class User {
  final String name;
  final int age;

  const User(this.name, this.age); //預設建構函式

}

void main() {
  var user = User("黃藥師", 50);
  print("----${user.name}${user.age}"); //輸出:黃藥師50
}
複製程式碼

工廠建構函式

當實現一個使用factory 關鍵詞修飾的建構函式時,這個建構函式不必建立類的新例項。例如,一個工廠建構函式 可能從快取中獲取一個例項並返回,或者 返回一個子型別的例項。(工廠建構函式無法訪問 this)

//工廠構造方法   如果一個構造方法並不總是返回一個新的物件,這個時候可以使用factory來定義這個構造方法。
class Logger {
  final String name;
  bool mute = false;

  static final Map<String, Logger> _cache = <String, Logger>{};

  factory Logger(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name];
    } else {
      final logger = new Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) {
      print(msg);
    }
  }
}

//呼叫
void main() {
  //工廠
  var logger = new Logger('UI');
  logger.log('Button clicked');
}
複製程式碼

要注意的是工廠構造方法時沒法訪問this關鍵字的,所以上面就有了在類的內部這麼呼叫構造方法的程式碼:final logger = new Logger._internal(name); 在上面工廠構造方法中,如果快取中存在傳入的name的key值,則取出快取中的對應value返回。 如果快取中沒找到,就會通過命名構造方法來新建一個物件,快取起來後返回

補充說明:藉助工廠建構函式能夠實現單例:(實用場景:flutter 網路請求Dio應用)

 //使用工廠構造實現單例
class DioUtil {
  static final DioUtil _instance = DioUtil._init();
  static Dio _dio;

  factory DioUtil() {
    return _instance;
  }

  DioUtil._init() {
    _dio = new Dio();
  }
}
複製程式碼

八、方法

上面是方法:方法是物件提供行為的函式。

Getters 和 Setters

Dart中每個例項變數都隱含的具有一個 getter, 如果變數不是 final 的則還有一個 setter。可以通過實現 getter 和 setter 來建立新的屬性, 使用 get 和 set 關鍵字定義 getter 和 setter:

class Rect {
  num left;
  num top;
  num width;
  num height;

  Rect(this.left, this.top, this.width, this.height);

  //使用 get定義了一個 right 屬性
  num get right             => left + width;
  set right(num value)  => left = value - width;
}

void main() {
  var rect = Rect(0, 0, 10, 10);
  print(rect.right); //10
  rect.right = 15;
  print(rect.left);  //5
}
複製程式碼

使用 Getter 和 Setter 的好處是,你可以先使用你的例項變數,過一段時間過再將它們包裹成方法且不需要改動任何程式碼,即先定義後更改且不影響原有邏輯。

九、抽象類、抽象方法、還有繼承

例項方法、Getter 方法以及 Setter 方法都可以是抽象的,定義一個介面方法而不去做具體的實現讓實現它的類去實現該方法,抽象方法只能存在於抽象類中,抽象類的定義跟Java的抽象類類似,就不單獨介紹了。

abstract class User {
  void say(); //定義一個抽象方法
}
class Person extends User{
  @override
  void say() {
    // 提供一個實現,所以在這裡該方法不再是抽象的……
  }

}
複製程式碼

說明:抽象類不能被例項化,除非定義工廠方法並返回子類。

abstract class User {
  String name;
  //預設構造方法
  User(this.name);
  //工廠方法返回Child例項
  factory User.test(String name){
    return new Child(name);
  }
  void printName();
}
// extends 繼承抽象類
class Child extends User{
  Child(String name) : super(name);

  @override
  void printName() {
    print(name);
  }
}

void main() {
  var p = User.test("黃藥師");
  print(p.runtimeType); //輸出實際型別 Child
  p.printName();//輸出實際型別 黃藥師
}
複製程式碼

十、介面

Dart 沒有像 Java 用單獨的關鍵字 interface 來定義介面,普通用 class 宣告的類就可以是介面,可以通過關鍵字 implements來實現一個或多個介面並實現每個介面定義的 API:

// Person 類的隱式介面中包含 greet() 方法。
class User {
  // _name 變數同樣包含在介面中,但它只是庫內可見的。
  final _name;

  // 建構函式不在介面中。
  User(this._name);

  // greet() 方法在介面中。
  String greet(String who) => '你好,$who。我是$_name。';
}

// Person 介面的一個實現。
class Impostor implements User {
  get _name => '';

  String greet(String who) => '你好$who。你知道我是誰嗎?';
}

String greetBob(User person) => person.greet('黃藥師');

void main() {
  print(greetBob(User('歐陽鋒'))); //輸出:你好,黃藥師。我是歐陽鋒。
  print(greetBob(Impostor())); //輸出:你好黃藥師。你知道我是誰嗎?
}
複製程式碼

這時疑問來了,介面跟繼承有什麼區別,不就是多繼承嗎? 介面的實現則意味著,子類獲取到的僅僅是介面的成員變數符號和方法符號,需要重新實現成員變數,以及方法的宣告和初始化,否則編譯器會報錯。而繼承可以選擇不重新實現,這是最大的區別。

可能小夥伴覺得有點繞:那我們總結一下。其實就兩句話:

  • 單繼承,多實現。
  • 繼承可以有選擇的重寫父類方法並且可以使用super,實現強制重新定義介面所有成員。

十一、可呼叫的類:

如果 Dart 類實現了 call() 函式則 可以當做方法來呼叫。

class User {
  call(String name, int age) => '$name $age!';
}

main() {
  var c = new User();
  var out = c("黃藥師",50);
  print(out); //輸出:黃藥師 50!
}
複製程式碼

十二、混合Mixins

在物件導向的世界中,我們最熟悉的莫過於class、 abstract class和interface。Dart作為一門現代物件導向程式設計語音,在原有的特性基礎上,新增了一些新的特性如:Mixins

什麼是Mixins:

簡單的理解,就是用來複用多個類之間的程式碼,減少耦合。我們直接來看一個例子。

我們在沒有使用Mixins的從前:

假設,我們現在正在開發一個動物大全App,我們需要建立一個Duck類。作為一個有豐富物件導向程式設計經驗的開發者,你自然的將所有和Duck有相似特徵的抽取成一個abstract class。

/// Bird
abstract class Bird {
    void shout() {
        println('shouting');
    }
}

/// WaterborneBird
abstract class WaterborneBird extends Bird {
    void swim() {
        println('swimming');
    }
}

/// Duck
class Duck extends WaterborneBird {
    void doDuckThings() {
        shout();
        swim();
        println('quack quack quack!')
    }
}
複製程式碼

很好,我們清楚的將鴨子歸入水中生活的鳥類,加入其它的鳥類也變得非常容易。但是,現在我們需要加入金魚了,於是我們和上面一樣編寫程式碼。

/// Fish
abstract class Fish {
    void swim() {
        println("swimming")
    }
}

/// GoldFish
class GoldFish extends Fish {
    void doGoldFishThings() {
        swim();
        pringln('zzz...');
    }
}
複製程式碼

這是我們發現金魚和鴨子一樣擁有swim的特性,在這個例子中是非常簡單的,但是如果我們有複雜的行為需要賦予給一個新的類,我們就要大量編寫重複的程式碼了。

使用Mixins

我們宣告一個Swimming的mixin

mixin Swimming {
    void swim() {
        println('swimming')
    }
}
複製程式碼

我們可以使用with關鍵字將mixin加入到class中,其實看到這裡你可能已經回想到我們其實可能已經用過這個with關鍵字了。接下來,我們就可以對上面的程式碼進行改造了:

/// Bird
abstract class Bird {
    void shout() {
        println('游泳');
    }
}


/// Duck
class Duck extends Bird with Swimming {
    void doDuckThings() {
        shout();
        swim();
        println('跳躍!')
    }
}
複製程式碼
/// Fish
abstract class Fish {

}

/// GoldFish
class GoldFish extends Fish with Swimming {
    void doGoldFishThings() {
        swim();
        pringln('zzz...');
    }
}
複製程式碼

mixins彌補了介面和繼承的不足,繼承只能單繼承,而介面無法複用實現,mixins卻可以多混入並且能利用到混入類。

我們在來看一個例子做比較:

abstract class Swimming{
  void swimming(){
    print("游泳");
  }
}

abstract class Jump{
  void jump(){
    print("跳躍");
  }
}

//只能單繼承,如果需要Jump,只能以implements的形式
class HuangYaoShi extends Swimming implements Jump{
  //實現介面
  void jump(){
    print("跳躍");
  }
}

//但是實際上,我們經常不需要重新實現Jump方法,複用Jump所實現的jump方法就可以了
//這時使用混合能夠更加方便
class HuangYaoShi with Swimming, Jump {}
複製程式碼

關於Mixins,還有很多需要注意的事情,我們雖然可以使用Mixins對程式碼進行一些簡化,但是要建立在對需求和類之間的關係準確理解的基礎上。建議多去看看Flutter中使用Mixins實現的一些原始碼,從裡面吸取一些正確的經驗。

下一章: 40分鐘快速入門Dart基礎(下)

參考:上面動物例子轉載了該博主的文章: 深入理解Dart之Mixins

最後附上:本人自己收集的工具庫(待完成) 工具庫-待完成

知乎: 如何快速掌握Dart這門語言並進階Flutter變成大神

相關文章