Flutter面試:Dart基礎2


theme: orange

文章目錄

Dart是不是單線程模型?是如何運行的?

一句話總結

  • Dart 是單線程模型,它的核心是事件循環(huán)機制,通過微任務隊列事件隊列來高效地處理異步操作,從而保證了UI線程的流暢性。

核心概念

Dart是單線程模型,不必像在Java或C#中那樣擔心共享內(nèi)存的鎖競爭和線程同步問題。運行機制可以從以下三個層面來理解:

1. 核心機制:事件循環(huán) (Event Loop)

Dart 的事件循環(huán)依賴兩個有序隊列,按優(yōu)先級執(zhí)行:

  • 微任務隊列 (Microtask Queue) :優(yōu)先級最高;通過 scheduleMicrotask() 顯式添加,附著在這個 Future 上的回調(diào)(包括 then、catchError、whenComplete 以及 await 的續(xù)延代碼)都會被調(diào)度到微任務隊列中執(zhí)行。
  • 事件隊列 (Event Queue) :我們?nèi)粘J褂玫慕^大部分異步操作都來源于這里,例如I/O操作(文件、網(wǎng)絡請求)、手勢識別、繪圖消息、Timer (Future.delayed)。

事件循環(huán)的工作流程圖:

flowchart TD
    A[事件循環(huán)啟動] --> B{微任務隊列<br>是否為空?}
    B -- 否 --> C[取出并執(zhí)行<br>一個微任務]
    C --> B
    B -- 是 --> D{事件隊列<br>是否為空?}
    D -- 否 --> E[取出并執(zhí)行<br>一個事件]
    E --> F[事件執(zhí)行完畢]
    F --> B
    D -- 是 --> G[等待新事件到來]
    G --> B

事件循環(huán)代碼演示:

void main() {
  // ------------------------------
  // 1. 同步代碼(優(yōu)先執(zhí)行,事件循環(huán)啟動前)
  // ------------------------------
  print("1. 同步代碼 - 開始執(zhí)行");

  // ------------------------------
  // 2. 微任務隊列(Microtask Queue):優(yōu)先級高于事件隊列
  //    通過 Future.microtask 添加
  // ------------------------------
  Future.microtask(() {
    print("2. 微任務A - 微任務隊列");
  });

  // ------------------------------
  // 3. 事件隊列(Event Queue):優(yōu)先級低于微任務隊列
  //    通過 Future 或 Future.delayed 添加(即使延遲0秒)
  // ------------------------------
  // 事件1:執(zhí)行時會新增微任務
  Future(() {
    print("4. 事件1 - 事件隊列(開始執(zhí)行)");
    // 在事件執(zhí)行過程中新增微任務
    Future.microtask(() {
      print("5. 微任務B(事件1中新增) - 微任務隊列");
    });
    print("6. 事件1 - 事件隊列(執(zhí)行結束)");
  });

  // 再添加一個微任務(微任務隊列按添加順序執(zhí)行)
  Future.microtask(() {
    print("3. 微任務C - 微任務隊列");
  });

  // 事件2:在事件1之后執(zhí)行
  Future.delayed(Duration.zero, () {
    print("7. 事件2 - 事件隊列");
  });

  // ------------------------------
  // 1. 同步代碼(繼續(xù)執(zhí)行)
  // ------------------------------
  print("8. 同步代碼 - 執(zhí)行結束(事件循環(huán)即將啟動)");
}

執(zhí)行結果:

1. 同步代碼 - 開始執(zhí)行
8. 同步代碼 - 執(zhí)行結束(事件循環(huán)即將啟動)
2. 微任務A - 微任務隊列
3. 微任務C - 微任務隊列
4. 事件1 - 事件隊列(開始執(zhí)行)
6. 事件1 - 事件隊列(執(zhí)行結束)
5. 微任務B(事件1中新增) - 微任務隊列
7. 事件2 - 事件隊列

步驟拆解(對應執(zhí)行結果):

  1. 同步代碼優(yōu)先執(zhí)行
    打印 18(同步代碼不進入任何隊列,事件循環(huán)啟動前就執(zhí)行完畢)。
  2. 事件循環(huán)啟動,先清空微任務隊列
    按添加順序執(zhí)行微任務 A 和 C,打印 23(微任務隊列此時為空)。
  3. 處理事件隊列的第一個事件(事件 1)
    • 執(zhí)行事件 1 的代碼,打印 46
    • 事件 1 執(zhí)行過程中新增微任務 B(加入微任務隊列)。
  4. 事件 1 執(zhí)行完畢后,再次清空微任務隊列
    執(zhí)行新增的微任務 B,打印 5(微任務隊列再次為空)。
  5. 處理事件隊列的下一個事件(事件 2)
    執(zhí)行事件 2,打印 7(事件隊列此時為空)。
  6. 所有隊列清空,程序結束

2. 實現(xiàn)異步的關鍵:Future 和 async/await

  • Future:它不是一個并行計算的結果,而是一個 “承諾” ,承諾在未來某個事件循環(huán)輪次中給你一個值(或一個錯誤)。當你調(diào)用http.get()Future.delayed()時,Dart會立刻返回一個Future對象,并立即返回(不會阻塞),I/O操作(網(wǎng)絡/文件)由操作系統(tǒng)異步處理(Dart線程之外),主線程(Dart線程)僅處理回調(diào)。
  • async/await:這只是編寫異步代碼的 “語法糖” ,讓異步代碼看起來和同步代碼一樣直觀。在函數(shù)前加上async關鍵字,其返回值會被自動包裝為Future。await關鍵字的作用是:告訴事件循環(huán)“我這邊有個Future需要一些時間才能完成,你先去處理其它事件(微任務或UI事件),等這個Future有結果了再回來繼續(xù)執(zhí)行我后面的代碼?!?/strong> 這非常重要,它不會阻塞線程,而是讓出執(zhí)行權。

代碼演示:

// 需安裝http庫 flutter pub add http
import 'package:http/http.dart' as http;

void loadData() async {
  print('1: 開始請求');
  var data = await http.get(
    Uri.parse('https://jsonplaceholder.typicode.com/posts/1'),
  ); // 不會阻塞,只是“等待”
  print('3: 收到數(shù)據(jù): $data');
}

void main() {
  loadData();
  print('2: 執(zhí)行其它操作');
}

執(zhí)行結果:

1: 開始請求
2: 執(zhí)行其它操作
3: 收到數(shù)據(jù): Instance of 'Response'

梳理流程:

  1. 發(fā)起請求(同步) :

    • http.get() 被調(diào)用。
    • 它同步地創(chuàng)建一個 Future 對象并返回。
    • 它同步地發(fā)起一個系統(tǒng)調(diào)用(比如用 libcurl 或系統(tǒng)的 HTTP 庫),將這個網(wǎng)絡 I/O 操作委托給操作系統(tǒng)在 Dart 線程之外執(zhí)行。此時,Dart 線程本身是空閑的,可以繼續(xù)執(zhí)行事件循環(huán)。
  2. 遇到 await(同步 -> 異步) :

    • await 關鍵字檢查它右邊的 Future。如果這個 Future 尚未完成(http.get 返回的肯定未完成),它會暫停當前函數(shù)的執(zhí)行。
    • 向這個未完成的 Future 訂閱一個回調(diào)。這個回調(diào)包含了 await 之后的所有代碼(即 print('3: ...'))。
    • 這個回調(diào)的調(diào)度機制,與 future.then(...) 的回調(diào)調(diào)度機制是完全相同的。
  3. 等待與完成(在Dart線程外) :

    • Dart 事件循環(huán)繼續(xù)運行,處理微任務和UI事件(比如執(zhí)行 print('2'))。
    • 操作系統(tǒng)在后臺處理網(wǎng)絡請求。
  4. 請求完成,通知Dart(事件隊列) :

    • 當操作系統(tǒng)完成網(wǎng)絡請求后,它會通過一種機制(如 epoll、kqueueIOCP)通知 Dart 運行時。
    • 這個“通知”作為一個事件,被放入 Dart 的事件隊列(Event Queue)中。 這個事件本身不包含數(shù)據(jù)處理邏輯,只是一個信號。
  5. 事件循環(huán)處理完成事件(從事件隊列到微任務隊列) :

    • 事件循環(huán)從事件隊列中取出這個“網(wǎng)絡請求完成”的事件。
    • 處理這個事件的代碼(Dart 運行時內(nèi)部代碼)會完成之前 http.get 返回的那個 Future 對象(即調(diào)用 _completer.complete(response))。
    • 最關鍵的一步來了:當 future.complete() 被調(diào)用時,Dart 的運行時會 schedule 一個微任務。這個微任務的任務就是:執(zhí)行所有通過 then、catchErrorawait 注冊在這個 Future 上的回調(diào)函數(shù)。
  6. 執(zhí)行回調(diào)(微任務隊列) :

    • 當事件循環(huán)處理完當前的事件后,它會檢查微任務隊列。
    • 它發(fā)現(xiàn)了第5步中被調(diào)度的微任務,于是執(zhí)行它。
    • 這個微任務的內(nèi)容就是執(zhí)行 print('3: 收到數(shù)據(jù): $data')。

操作系統(tǒng)完成I/O -> 發(fā)送信號至 Dart 事件隊列 -> 事件循環(huán)處理該信號 -> Future.complete()被調(diào)用 -> 調(diào)度一個微任務來執(zhí)行所有回調(diào) -> 事件循環(huán)處理微任務 -> 執(zhí)行 await 之后的代碼。

3. 處理真正耗時計算:Isolate

事件循環(huán)和異步處理對于I/O操作是完美的,但對于CPU密集型計算(如圖像處理、加密解密、復雜JSON解析),如果計算本身就在Dart線程上運行,它就會阻塞事件循環(huán),導致隊列中的所有其他任務(包括UI渲染)都無法處理,應用就會卡頓。

Dart的解決方案是 Isolate。

  • 不共享內(nèi)存:每個Isolate都有自己的獨立內(nèi)存堆(Heap)和事件循環(huán)。Isolate之間不共享任何狀態(tài),通信的唯一方式是通過消息傳遞(Passing Messages) 。
  • 真正的并行:由于現(xiàn)代設備是多核CPU,不同的Isolate可以被調(diào)度到不同的CPU核心上真正地并行運行。
  • 通信成本:因為不共享內(nèi)存,Isolate之間傳遞消息時,數(shù)據(jù)會發(fā)生拷貝。雖然對于簡單數(shù)據(jù)很快,但對于大型數(shù)據(jù)(如圖片、大列表),這個拷貝成本會很高。通常使用SendPortReceivePort進行通信。

在Flutter中,你可以使用Isolate.spawn()或更高級的compute()函數(shù)來將繁重任務拋到新的Isolate中執(zhí)行。

代碼演示:

import 'package:flutter/foundation.dart';

// 使用 compute (Flutter提供的簡便API)
void main() async {
  var result = await compute(heavyTask, 1000000000);
  print(result);
}

// 這個函數(shù)會在新的Isolate中執(zhí)行
int heavyTask(int count) {
  int sum = 0;
  for (int i = 0; i < count; i++) {
    sum += i;
  }
  return sum;
}


dart是值傳遞還是引用傳遞?

一句話總結

  • Dart 是嚴格的值傳遞語言,所有參數(shù)傳遞的都是 “值的副本”。

核心概念

Dart 中的數(shù)據(jù)類型分為基本類型(Primitive Types)對象類型(Object Types) ,兩者在值傳遞時的表現(xiàn)不同,容易造成 “引用傳遞” 的誤解:

1. 基本類型(數(shù)值、布爾、字符串、null)

基本類型的值傳遞是直接傳遞值的副本,函數(shù)內(nèi)部修改參數(shù)不會影響外部變量。

代碼演示:

void modifyInt(int a) {
  a = 100; // 修改的是副本
  print("函數(shù)內(nèi):$a"); // 輸出:100
}

void main() {
  int x = 10;
  modifyInt(x); // 傳遞x的副本(值為10)
  print("函數(shù)外:$x"); // 輸出:10(原始值未變)
}
  • 解析:x的值10被復制一份傳遞給a,函數(shù)內(nèi)修改a不會影響x,符合值傳遞特性。

2. 對象類型(類實例、列表、映射等)

對象類型的值傳遞是傳遞對象引用的副本(即內(nèi)存地址的副本)。這意味著:

  • 函數(shù)內(nèi)部和外部的變量持有同一個對象的不同引用副本(但指向同一塊內(nèi)存)。
  • 若修改對象的內(nèi)部狀態(tài)(如屬性、元素),會影響外部變量(因為指向同一對象)。
  • 若直接修改參數(shù)的引用指向(如重新賦值新對象),則不會影響外部變量(因為修改的是副本引用)。

代碼演示:

示例 1:修改對象內(nèi)部狀態(tài)

class Person {
  String name;
  Person(this.name);
}

void modifyPerson(Person p) {
  p.name = "Bob"; // 修改對象內(nèi)部狀態(tài)(影響外部)
  print("函數(shù)內(nèi):${p.name}"); // 輸出:Bob
}

void main() {
  Person person = Person("Alice");
  modifyPerson(person); // 傳遞person引用的副本(指向同一對象)
  print("函數(shù)外:${person.name}"); // 輸出:Bob(內(nèi)部狀態(tài)被修改)
}
  • person 變量持有一個指向 Person('Alice') 對象的引用(可以理解為內(nèi)存地址)。
  • 當調(diào)用 modifyObject(person) 時,傳遞的是這個引用的值(即內(nèi)存地址)的一個副本
  • 現(xiàn)在,有兩個引用指向同一個對象:外部的 person 和函數(shù)內(nèi)部的 p
  • 通過函數(shù)內(nèi)部的引用 p 去修改對象的屬性(p.name = 'Bob'),因為外部引用 person 也指向這同一個對象,所以這個修改對兩者都是可見的。這常常被誤認為是“引用傳遞”。

示例 2:修改參數(shù)的引用指向

class Person {
  String name;
  Person(this.name);
}

void replacePerson(Person p) {
  p = Person("Charlie"); // 修改引用副本的指向(不影響外部)
  print("函數(shù)內(nèi):${p.name}"); // 輸出:Charlie
}

void main() {
  Person person = Person("Alice");
  replacePerson(person);
  print("函數(shù)外:${person.name}"); // 輸出:Alice(原始引用未變)
}
  • 同樣,開始時內(nèi)部的 p 和外部的 person 指向同一個對象。
  • 但當執(zhí)行 p = Person('Charlie') 時,你是在讓內(nèi)部的引用 p(它只是外部引用的一個副本)指向一個全新的對象。
  • 并沒有改變原來那個對象(Person('Alice'))的任何狀態(tài),也沒有改變外部引用 person 的值。外部引用 person 依然堅定地指向最初的那個對象。所以外部的打印結果沒有變化。

對 Flutter 開發(fā)的實際影響

  1. 不可變對象的優(yōu)勢
    對于String、int等不可變類型(以及自定義不可變類),值傳遞時無需擔心內(nèi)部狀態(tài)被修改,適合作為狀態(tài)管理中的數(shù)據(jù)載體(如ProviderBloc中的狀態(tài))。

  2. 列表 / 映射的傳遞注意事項
    傳遞ListMap時,若需避免函數(shù)內(nèi)部修改原集合,應傳遞副本(如List.from(list)Map.from(map)):

    void safeModify(List<int> list) {
      list = List.from(list); // 創(chuàng)建副本后再修改
      list.add(4);
    }
    
  3. 狀態(tài)管理中的數(shù)據(jù)更新
    在 Flutter 狀態(tài)管理中,修改對象內(nèi)部狀態(tài)后若需觸發(fā) UI 重建,需確保狀態(tài)引用發(fā)生變化(如創(chuàng)建新對象),因為 Widget 會對比引用是否相同來決定是否重建。



mixin extends implement之間的關系。

一句話總結

  • extends 用于繼承,建立‘is-a’的關系,繼承并可重寫父類實現(xiàn),但受限于單繼承。
  • implements:用于實現(xiàn)接口,建立 “can-do” 的關系,必須重寫接口所有成員,可以實現(xiàn)多個接口。
  • mixin/with 用于混入代碼,建立 “包含” 的關系,直接復用方法實現(xiàn),解決單繼承局限,后寫的覆蓋先寫的,可以混入多個。

核心概念

1. extends:類的單繼承("是一個" 的關系)

  • 單繼承:Dart 是單繼承語言,一個類只能直接繼承一個父類
  • 繼承實現(xiàn):子類繼承父類的所有非私有成員(字段、方法)
  • 構造函數(shù)不繼承:子類不繼承父類的構造函數(shù),但可以調(diào)用它們
  • 方法重寫:子類可以重寫父類的方法

代碼演示:

基本繼承示例
class Animal {
  String name;
  int age;
  
  Animal(this.name, this.age);
  
  void speak() {
    print('Animal sound');
  }
  
  void sleep() {
    print('$name is sleeping');
  }
}

class Dog extends Animal {
  String breed;
  
  // 調(diào)用父類構造函數(shù)
  Dog(String name, int age, this.breed) : super(name, age);
  
  // 重寫父類方法
  @override
  void speak() {
    print('Woof! Woof!');
  }
  
  // 添加新方法
  void fetch() {
    print('$name is fetching a ball');
  }
}

void main() {
  var dog = Dog('Buddy', 3, 'Golden Retriever');
  dog.speak();    // 輸出: Woof! Woof! (重寫的方法)
  dog.sleep();    // 輸出: Buddy is sleeping (繼承的方法)
  dog.fetch();    // 輸出: Buddy is fetching a ball (新方法)
}
繼承中的構造函數(shù)處理
class Person {
  String name;
  int age;
  
  Person(this.name, this.age);
  
  Person.newborn(String name) : this(name, 0);
  
  void introduce() {
    print('Hi, I'm $name, $age years old');
  }
}

class Employee extends Person {
  String company;
  
  Employee(String name, int age, this.company) : super(name, age);
  
  // 子類的命名構造函數(shù)
  Employee.freshGraduate(String name, String company) 
      : this(name, 22, company);
  
  @override
  void introduce() {
    super.introduce();
    print('I work at $company');
  }
}
屬性重寫(Getter/Setter)
import 'dart:math';

class Rectangle {
  double width;
  double height;
  
  Rectangle(this.width, this.height);
  
  double get area => width * height;
  
  set area(double value) {
    // 保持寬高比例
    final ratio = width / height;
    width = sqrt(value * ratio);
    height = width / ratio;
  }
}

class Square extends Rectangle {
  Square(double side) : super(side, side);
  
  @override
  double get area => super.area;
  
  @override
  set area(double value) {
    // 正方形面積設置
    width = sqrt(value);
    height = width;
  }
}
繼承抽象類
abstract class Animal {
  String name;
  
  Animal(this.name);
  
  void makeSound(); // 抽象方法
  
  void sleep() {
    print('$name is sleeping');
  }
}

class Cat extends Animal {
  Cat(String name) : super(name);
  
  @override
  void makeSound() {
    print('Meow!');
  }
}

class Dog extends Animal {
  Dog(String name) : super(name);
  
  @override
  void makeSound() {
    print('Woof!');
  }
  
  @override
  void sleep() {
    super.sleep(); // 調(diào)用父類實現(xiàn)
    print('...and dreaming about bones');
  }
}
繼承泛型類
class Box<T> {
  T content;
  
  Box(this.content);
  
  T getContent() => content;
}

class NumberBox extends Box<int> {
  NumberBox(int value) : super(value);
  
  // 方法可以操作具體類型
  int square() => content * content;
}

class PairBox<A, B> extends Box<A> {
  B second;
  
  PairBox(A first, this.second) : super(first);
  
  (A, B) getPair() => (content, second);
}
繼承中的 super 關鍵字
class Vehicle {
  void start() {
    print('Vehicle starting');
  }
}

class Car extends Vehicle {
  @override
  void start() {
    super.start(); // 調(diào)用父類方法
    print('Car specific startup procedure');
  }
}

class ElectricCar extends Car {
  @override
  void start() {
    print('Electric systems check');
    super.start(); // 調(diào)用父類(Car)的方法
  }
}
繼承中的靜態(tài)成員

靜態(tài)成員不會被繼承:

class MathUtils {
  static double pi = 3.14159;
  
  static double circleArea(double radius) {
    return pi * radius * radius;
  }
}

class AdvancedMathUtils extends MathUtils {
  // 靜態(tài)成員不會被繼承
  // 不能直接訪問 pi 或 circleArea
  
  static double e = 2.71828;
  
  // 必須重新定義靜態(tài)方法
  static double circleArea(double radius) {
    return MathUtils.pi * radius * radius; // 通過類名訪問
  }
}

void main() {
  print(MathUtils.pi);           // 3.14159
  // print(AdvancedMathUtils.pi);   // 錯誤: 沒有這樣的getter
  print(AdvancedMathUtils.e);    // 2.71828
}

繼承的最佳實踐

  1. 遵循里氏替換原則:子類應該能夠替換父類而不影響程序正確性
  2. 使用繼承表示"是一個"關系:子類應該是父類的特殊化
  3. 優(yōu)先使用組合 over 繼承:如果不是真正的"是一個"關系,考慮使用組合
  4. 避免深繼承層次:深層次的繼承難以理解和維護
  5. 使用抽象類定義接口:為預期會被繼承的類提供清晰的契約

2. implements:接口實現(xiàn)("像一個" 的關系)

  • 接口契約:當一個類實現(xiàn)一個接口時,需實現(xiàn)接口中所有非靜態(tài)成員(方法、屬性、getter、setter)。
  • 不繼承實現(xiàn):與 extends 不同,implements 不繼承任何實現(xiàn),只遵循接口定義的 “規(guī)范”。
  • 多接口實現(xiàn):一個類可以實現(xiàn)多個接口

代碼演示:

實現(xiàn)普通類的接口:
class Vehicle {
  String name;
  int speed;
  
  Vehicle(this.name, this.speed);
  
  void move() {
    print('$name is moving at $speed km/h');
  }
  
  void stop() {
    print('$name has stopped');
    speed = 0;
  }
}

class Car implements Vehicle {
  @override
  String name;
  
  @override
  int speed;
  
  Car(this.name, this.speed);
  
  @override
  void move() {
    print('Car $name is driving at $speed km/h');
  }
  
  @override
  void stop() {
    print('Car $name is braking');
    speed = 0;
  }
}
實現(xiàn)抽象類的接口:
abstract class Animal {
  String name;
  int age;
  
  Animal(this.name, this.age);
  
  void makeSound(); // 抽象方法
  
  void sleep() {
    print('$name is sleeping');
  }
}

class Dog implements Animal {
  @override
  String name;
  
  @override
  int age;
  
  Dog(this.name, this.age);
  
  @override
  void makeSound() {
    print('$name says: Woof!');
  }
  
  @override
  void sleep() {
    print('Dog $name is sleeping soundly');
  }
}
靜態(tài)成員在接口實現(xiàn)中的特殊行為

在 Dart 中,靜態(tài)成員不是接口的一部分。當一個類實現(xiàn)另一個類或接口時,它不需要實現(xiàn)靜態(tài)成員。這是因為靜態(tài)成員屬于類本身,而不是類的實例。

class ExampleWithStatic {
  // 實例成員
  String instanceField = 'instance';
  void instanceMethod() => print('Instance method');

  // 靜態(tài)成員
  static String staticField = 'static';
  static void staticMethod() => print('Static method');
}

class Implementation implements ExampleWithStatic {
  @override
  String instanceField = 'implemented instance';

  @override
  void instanceMethod() {
    print('Implemented instance method');
  }

// 注意:不需要實現(xiàn)靜態(tài)成員
// static String staticField = 'something'; // 這不是必需的
// static void staticMethod() {} // 這不是必需的
}
實現(xiàn)多個接口
abstract class Flyable {
  void fly();
}

abstract class Swimmable {
  void swim();
}

abstract class Walkable {
  void walk();
}

class Duck implements Flyable, Swimmable, Walkable {
  @override
  void fly() {
    print('Duck is flying');
  }
  
  @override
  void swim() {
    print('Duck is swimming');
  }
  
  @override
  void walk() {
    print('Duck is waddling');
  }
}
實現(xiàn)混合接口
// 普通類
class Named {
  String name;
  Named(this.name);
}

// 抽象類
abstract class Aged {
  int get age;
  set age(int value);
}

// Mixin
mixin Colored {
  String color = 'unknown';
  
  void describeColor() {
    print('Color: $color');
  }
}

// 實現(xiàn)多個接口
class Person implements Named, Aged, Colored {
  @override
  String name;
  
  @override
  int age;
  
  @override
  String color;
  
  Person(this.name, this.age, this.color);
  
  // Colored mixin 的方法也需要實現(xiàn)
  @override
  void describeColor() {
    print('Person $name has $color color');
  }
}
處理私有成員
// 在同一個庫中
class _PrivateClass {
  int _privateValue = 0; // 私有字段
  int publicValue = 0;   // 公共字段
  
  void _privateMethod() {} // 私有方法
  void publicMethod() {}   // 公共方法
}

// 這會報錯,因為需要實現(xiàn)私有成員
class Implementation implements _PrivateClass {
  @override
  int publicValue = 0;
  
  @override
  void publicMethod() {}
  
  // 錯誤: 缺少私有成員的實現(xiàn)
}
實現(xiàn)泛型接口
abstract class Repository<T> {
  T findById(int id);
  List<T> findAll();
  void save(T entity);
  void delete(int id);
}

class UserRepository implements Repository<User> {
  final List<User> _users = [];
  
  @override
  User findById(int id) {
    return _users.firstWhere((user) => user.id == id, orElse: () => null);
  }
  
  @override
  List<User> findAll() {
    return List.from(_users);
  }
  
  @override
  void save(User user) {
    final index = _users.indexWhere((u) => u.id == user.id);
    if (index >= 0) {
      _users[index] = user;
    } else {
      _users.add(user);
    }
  }
  
  @override
  void delete(int id) {
    _users.removeWhere((user) => user.id == id);
  }
}

class User {
  final int id;
  final String name;
  
  User(this.id, this.name);
}
處理 getter 和 setter
abstract class Person {
  String get firstName;
  set firstName(String value);
  String get lastName;
  set lastName(String value);
  String get fullName; // 只讀屬性
}

class Employee implements Person {
  String _firstName;
  String _lastName;
  
  Employee(this._firstName, this._lastName);
  
  @override
  String get firstName => _firstName;
  
  @override
  set firstName(String value) {
    _firstName = value;
  }
  
  @override
  String get lastName => _lastName;
  
  @override
  set lastName(String value) {
    _lastName = value;
  }
  
  @override
  String get fullName => '$firstName $lastName';
}

實現(xiàn)接口時的最佳實踐:

  1. 明確接口契約:使用抽象類或純接口定義清晰的契約
  2. 避免實現(xiàn)包含私有成員的類:這會導致難以維護的代碼
  3. 使用組合替代復雜接口實現(xiàn):當需要復用多個類的功能時,考慮使用組合
  4. 保持接口簡潔:遵循接口隔離原則,創(chuàng)建小而專注的接口
  5. 文檔化接口:為接口提供清晰的文檔說明預期行為
// 好的實踐:小而專注的接口
abstract class Savable {
  void save();
}

abstract class Deletable {
  void delete();
}

abstract class Findable<T> {
  T findById(int id);
}

class User {
  final int id;
  final String name;

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

// 實現(xiàn)多個小接口
class UserRepository implements Savable, Deletable, Findable<User> {
  @override
  void delete() {
    // TODO: implement delete
  }

  @override
  User findById(int id) {
    // TODO: implement findById
    throw UnimplementedError();
  }

  @override
  void save() {
    // TODO: implement save
  }
  // 實現(xiàn)...
}

mixin:代碼復用("包含" 的關系)

  • mixin關鍵字定義,不能有構造函數(shù)(避免與混入類的構造函數(shù)沖突)。
  • 一個類可通過with關鍵字混入多個 mixin(按順序生效,后混入的 mixin 會覆蓋先混入的同名方法)。
  • 可通過on關鍵字限制 mixin 的使用范圍(如mixin M on A表示 M 只能混入 A 的子類)。

代碼演示:

聲明限制

Mixin 不能有構造函數(shù),也不能被實例化:

mixin Logger {
  // 不能有構造函數(shù)
  // Logger(); // 錯誤: Mixin 不能聲明構造函數(shù)
  
  void log(String message) {
    print('LOG: $message');
  }
}
使用 on 關鍵字限制應用范圍
class Animal {
  void breathe() {
    print('Breathing');
  }
}

// 這個 Mixin 只能用于 Animal 或其子類
mixin Swimming on Animal {
  void swim() {
    print('Swimming');
    breathe(); // 可以訪問 Animal 的方法
  }
}

// 正確: Bird 是 Animal 的子類
class Bird extends Animal with Swimming {}

// 錯誤: Robot 不是 Animal 的子類
// class Robot with Swimming {} // 編譯錯誤
Mixin 的線性化

Dart 使用線性化算法來確定方法解析順序。當類使用多個 Mixin 時,方法的解析順序是從右到左:

mixin A {
  void method() {
    print('A.method');
  }
}

mixin B {
  void method() {
    print('B.method');
  }
}

mixin C {
  void method() {
    print('C.method');
  }
}

class MyClass with A, B, C {
  // 方法解析順序: MyClass -> C -> B -> A -> Object
}

void main() {
  var obj = MyClass();
  obj.method(); // 輸出: C.method (最右邊的 Mixin 優(yōu)先)
}

Mixin 的最佳實踐

  1. 單一職責:每個 Mixin 應該只負責一個特定的功能
  2. 命名清晰:使用描述性的名稱,通常以 "-able" 或 "-er" 結尾
  3. 避免狀態(tài)污染:謹慎使用實例變量,避免意外的狀態(tài)共享
  4. 文檔化約束:使用 on 關鍵字明確指定 Mixin 的使用范圍
  5. 測試獨立:確保 Mixin 可以獨立測試,不依賴于特定的類層次結構


Dart中如何取消正在執(zhí)行中的異步任務?

一句話總結

  • 通過 Completer 來控制 Future 的完成狀態(tài)。
  • 通過CancelableOperation 包裝Future,實現(xiàn) “邏輯取消”。
  • 基于流的操作,可以使用 StreamSubscription 的取消機制。
  • 對于計算密集型任務,使用 Isolate 通過kill()方法強制終止。

核心概念

1. 使用 FutureCompleter 實現(xiàn)取消

Completer是 Dart 中用于手動控制Future完成狀態(tài)的工具類,它可以主動觸發(fā)Future的完成(成功 / 失敗)。

import 'dart:async';

class CancellableTask {
  Completer<void> _completer = Completer<void>();

  Future<void> execute() async {
    _completer = Completer<void>();
    _doWork();
  }

  Future<void> _doWork() async {
    for (int i = 0; i < 100; i++) {
      // 檢查是否被取消
      if (_completer.isCompleted) {
        print('任務被取消');
        return;
      }

      await Future.delayed(Duration(milliseconds: 100));

      print('處理項目 $i');
    }
    return _completer.future;
  }

  void cancel() {
    if (!_completer.isCompleted) {
      _completer.complete();
    }
  }
}

// 使用示例
void main() async {
  final task = CancellableTask();

  // 啟動任務
  task.execute();

  // 2秒后取消
  await Future.delayed(Duration(seconds: 2));
  task.cancel();
}

2. 使用 CancelableOperation (package:async)

  • 需要安裝async庫。CancelableOperation 本質(zhì)是對Future的封裝,通過一個 “取消令牌” 標記任務狀態(tài)。取消后,后續(xù)的then/whenComplete等回調(diào)不會執(zhí)行,且會觸發(fā)onCancel回調(diào)釋放資源。
  • 第三方庫(如dio)通常封裝了取消機制,本質(zhì)是基于CancelableOperation或標志位。
import 'dart:async';
import 'package:async/async.dart';

// 模擬耗時任務(如網(wǎng)絡請求)
Future<String> fetchData() async {
  await Future.delayed(Duration(seconds: 3)); // 模擬3秒耗時
  return "請求結果";
}

void main() async {
  // 包裝Future為可取消操作
  final cancelable = CancelableOperation.fromFuture(
    fetchData(),
    onCancel: () {
      // 取消時釋放資源(如關閉網(wǎng)絡連接)
      print("任務已取消,釋放資源");
    },
  );

  // 1秒后取消任務
  Timer(Duration(seconds: 1), () {
    cancelable.cancel(); // 取消操作
  });

  // 監(jiān)聽結果(取消后不會執(zhí)行)
  final result = await cancelable.value;
  print("任務完成:$result");
}
  • 取消后,原始Future可能仍在后臺執(zhí)行(無法強制終止),但結果會被忽略。
  • 適合處理 “即使取消也不會造成嚴重資源浪費” 的任務(如短時間網(wǎng)絡請求)。

3.Stream任務的取消:通過StreamSubscription控制

通過stream.listen()獲取StreamSubscription對象,調(diào)用其cancel()方法可終止流的事件傳遞,且會觸發(fā)流的onCancel回調(diào)釋放資源。

import 'dart:async';

void main() {
  // 創(chuàng)建定時發(fā)送事件的Stream(每1秒一次)
  final stream = Stream.periodic(Duration(seconds: 1), (count) => count);

  // 訂閱流,獲取訂閱對象
  final subscription = stream.listen(
    (data) => print("接收事件:$data"),
    onDone: () => print("流已完成"),
  );

  // 3秒后取消訂閱
  Timer(Duration(seconds: 3), () {
    subscription.cancel(); // 取消后,不再接收事件
    print("流已取消");
  });
}
// 輸出:
// 接收事件:0
// 接收事件:1
// 接收事件:2
// 流已取消

4.Isolate任務的取消:通過kill()強制終止

Isolate擁有獨立的內(nèi)存和執(zhí)行線程,kill()會直接終止其運行,釋放所有資源,終止后無法恢復。

import 'dart:async';
import 'dart:isolate';

// 耗時計算任務(在Isolate中執(zhí)行)
void heavyCompute(SendPort sendPort) {
  int result = 0;
  for (int i = 0; i < 1000000000; i++) {
    result += i;
    // 定期檢查是否需要退出(可選,優(yōu)化終止響應速度)
    if (i % 100000000 == 0) {
      print("計算中:$i");
    }
  }
  sendPort.send(result);
}

void main() async {
  // 創(chuàng)建端口接收結果
  final receivePort = ReceivePort();
  // 啟動Isolate
  final isolate = await Isolate.spawn(heavyCompute, receivePort.sendPort);

  // 監(jiān)聽結果
  receivePort.listen((data) {
    print("計算結果:$data");
    receivePort.close();
  });

  // 2秒后終止Isolate(取消任務)
  Timer(Duration(seconds: 2), () {
    isolate.kill(priority: Isolate.immediate); // 立即終止
    print("Isolate已終止");
    receivePort.close();
  });
}
  • kill()是 “暴力終止”,需確保 Isolate 中沒有未釋放的資源(如文件句柄)。
  • 建議在 Isolate 內(nèi)部添加 “取消檢查點”(如定期檢查標志),配合kill()提升終止效率。


Dart中Future和Stream區(qū)別?

一句話總結

  • Future:處理單次異步結果,表示一個未來可能完成的單個異步操作,結果只有兩種狀態(tài) ——“成功返回一個值” 或 “失敗拋出一個錯誤”。
  • Stream:處理連續(xù)異步事件流,表示一個異步事件序列,可以持續(xù)產(chǎn)生多個值(或錯誤),最終可能正常結束或出錯終止。

核心概念

用一個簡單的比喻:

  • Future 就像是一次性的外賣訂單:你下單,等待,然后收到一份完整的餐點。
  • Stream 就像是餐廳的傳送帶:食物(數(shù)據(jù))會連續(xù)不斷地傳送過來,你可以隨時拿取。

代碼演示:

Future 示例:獲取單個異步結果

class User {
  final int id;
  final String name;
  User({required this.id, required this.name});
}

// Future: 獲取用戶信息(單次操作)
Future<User> fetchUserData(int userId) async {
  // 模擬網(wǎng)絡請求
  await Future.delayed(Duration(seconds: 2));
  return User(id: userId, name: 'John Doe');
}

// 使用
void main() async {
  print('開始獲取用戶數(shù)據(jù)...');
  final user = await fetchUserData(1);
  print('用戶數(shù)據(jù): ${user.name}'); // 單次結果
}

Stream 示例:監(jiān)聽連續(xù)的數(shù)據(jù)流

class Location {
  final double latitude;
  final double longitude;

  Location({required this.latitude, required this.longitude});
}

// Stream: 監(jiān)聽實時位置更新
Stream<Location> getLocationUpdates() async* {
  // 模擬連續(xù)的位置更新
  for (int i = 0; i < 5; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield Location(latitude: 37.0 + i * 0.1, longitude: -122.0 + i * 0.1);
  }
}

// 使用
void main() {
  print('開始監(jiān)聽位置更新...');
  final subscription = getLocationUpdates().listen(
    (location) {
      print('位置更新: ${location.latitude}, ${location.longitude}');
    },
    onDone: () => print('監(jiān)聽完成'),
    onError: (error) => print('錯誤: $error'),
  );

  // 可以隨時取消監(jiān)聽
  // subscription.cancel();
}

Future 的典型使用場景:

// 網(wǎng)絡請求:HTTP API 調(diào)用
Future<Response> response = http.get(Uri.parse('https://api.example.com/data'));
// 文件讀寫:讀取/寫入文件
Future<String> contents = File('data.txt').readAsString();
// 數(shù)據(jù)庫操作:單次查詢
Future<User> user = database.findUser(1);
// 用戶交互:顯示對話框
Future<bool> choice = showDialog(
  context: context,
  builder: (context) => AlertDialog(...),
);

Stream 的典型使用場景:

// 實時數(shù)據(jù):WebSocket、Firestore 實時更新
Stream<QuerySnapshot> stream = Firestore.instance.collection('messages').snapshots();
// 用戶輸入:搜索框輸入監(jiān)聽
searchController.textChanges
    .debounceTime(Duration(milliseconds: 300))
    .listen((query) => search(query));
// 傳感器數(shù)據(jù):位置、加速度計更新
Stream<Location> locationStream = Geolocator.getPositionStream();
// 狀態(tài)管理:BLoC、Riverpod 的狀態(tài)流
Stream<AppState> appStateStream = bloc.stream;

性能和使用建議:

何時選擇 Future

  • 只需要單次異步結果時
  • 操作有明確的開始和結束時
  • 不需要中間狀態(tài)更新時
  • 簡單的異步操作

何時選擇 Stream

  • 需要處理連續(xù)的數(shù)據(jù)序列時
  • 需要實時更新和狀態(tài)變化時
  • 需要取消和暫停機制時
  • 復雜的事件處理場景
  1. 避免 Stream 過度使用:如果 Future 足夠,不要使用 Stream。
  2. 及時取消訂閱:防止內(nèi)存泄漏。
  3. 使用 StreamBuilder 優(yōu)化 UI。
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • [TOC] Dart基礎 在Dart中,所有能夠使用變量引用的都是對象object,每個對象都是一個類class的...
    FreeRain77閱讀 983評論 0 1
  • 以下主要是學習極客時間 Flutter 專欄相關學習記錄。 Dart 基礎 Online Dart iDE 核心特...
    微微笑的蝸牛閱讀 1,124評論 0 51
  • 一、flutter啟動流程1.實例化WidgetsFlutterBinding類,2.創(chuàng)建組件樹attachRoo...
    齊玉婷閱讀 3,226評論 0 9
  • Dart 當中的 「..」表示什么意思? Dart 當中的 「..」意思是 「級聯(lián)操作符」,為了方便配置而使用?!?..
    kadis閱讀 631評論 0 4
  • Dart 相關 1、Dart 當中的 「..」表示什么意思? 級連操作符 “..” 和 “.” 不同:調(diào)用..后返...
    af06e7def7a7閱讀 2,578評論 0 2

友情鏈接更多精彩內(nèi)容