Flutter(able) 的單例模式

文/ 楊加康,CFUG 社區成員,《Flutter 開發之旅從南到北》做者,小米工程師html

單例設計模式(Singleton Design Pattern)理解起來很是簡單。segmentfault

一個類只容許建立一個實例,那這個類就是一個單例類,這種設計模式就叫做單例設計模式,簡稱單例模式。

做爲最簡單的一種設計模式之一,對於單例自己的概念,你們一看就能明白,但在某些狀況下也很容易使用不恰當。相比其餘語言,Dart 和 Flutter 中的單例模式也不盡相同,本篇文章咱們就一塊兒探究看看它在 Dart 和 Flutter 中的應用。設計模式

Flutter(able) 的單例模式

通常來講,要在代碼中使用單例模式,結構上會有下面這些約定俗成的要求:安全

  • 單例類(Singleton)中包含一個引用自身類的靜態屬性實例(instance),且能自行建立這個實例。
  • 該實例只能經過靜態方法 getInstance() 訪問。
  • 類構造函數一般沒有參數,且被標記爲私有,確保不能從類外部實例化該類。

單例設計模式 UML 圖,圖源:https://www.uml-diagrams.org/class-reference.html

遵循以上這些要求,咱們就不難能用 Dart 寫出一個普通的單例模式:多線程

class Singleton {
  static Singleton _instance;
  
  // 私有的命名構造函數
  Singleton._internal();
  
  static Singleton getInstance() {
    if (_instance == null) {
      _instance = Singleton._internal();
    }
    
    return _instance;
  }
}

同時,在實現單例模式時,也須要考慮以下幾點,以防在使用過程當中出現問題:併發

  • 是否須要懶加載,即類實例只在第一次須要時建立。
  • 是否線程安全,在 Java、C++ 等多線程語言中須要考慮到多線程的併發問題。因爲 Dart 是單線程模型的語言,全部的代碼一般都運行在同一個 isolate 中,所以不須要考慮線程安全的問題。
  • 在某些狀況下,單例模式會被認爲是一種 反模式,由於它違反了 SOLID 原則中的單一責任原則,單例類本身控制了本身的建立和生命週期,且單例模式通常沒有接口,擴展困難。
  • 單例模式的使用會影響到代碼的可測試性。若是單例類依賴比較重的外部資源,好比 DB,咱們在寫單元測試的時候,但願能經過 mock 的方式將它替換掉。而單例類這種硬編碼式的使用方式,致使沒法實現 mock 替換。

在實際編碼過程當中,單例模式常見應用有:函數

  • 全局日誌的 Logger 類、應用全局的配置數據對象類,單業務管理類。
  • 建立實例時佔用資源較多,或實例化耗時較長的類。
  • 等等...

Dart 化

如上文所說的,Dart 語言做爲單線程模型的語言,實現單例模式時,咱們自己已經能夠不用再去考慮 線程安全 的問題了。Dart 的不少其餘特性也依然能夠幫助到咱們實現更加 Dart 化的單例。單元測試

使用 getter 操做符,能夠打破單例模式中既定的,必定要寫一個 getInstance() 靜態方法的規則,簡化咱們必需要寫的模版化代碼,以下的 get instance:學習

class Singleton {
  static Singleton _instance;
  static get instance {
    if (_instance == null) {
      _instance = Singleton._internal();
    }
    
    return _instance;
  }
  
  Singleton._internal();
}

Dart 的 getter 的使用方式與普通方法大體相同,只是調用者再也不須要使用括號,這樣,咱們在使用時就能夠直接使用以下方式拿到這個單例對象:測試

final singleton = Singleton.instance;

而 Dart 中特有的 工廠構造函數(factory constructor)也原生具有了 沒必要每次都去建立新的類實例 的特性,將這個特性利用起來,咱們就能夠寫出更優雅的 Dart(able) 單例模式了,以下:

class Singleton {
  static Singleton _instance;
  
  Singleton._internal();
  
  // 工廠構造函數
  factory Singleton() {
    if (_instance == null) {
      _instance = Singleton._internal();
    }
    
    return _instance;
  }
}

這裏咱們再也不使用 getter 操做符額外提供一個函數,而是將單例對象的生成交給工廠構造函數,此時,工廠構造函數僅在第一次須要時建立 _instance,並以後每次返回相同的實例。這時,咱們就能夠像下面這樣使用普通構造函數的方式獲取到單例了:

final singleton = Singleton();

若是你還掌握了 Dart 空安全及箭頭函數等特性,那麼還可使用另外一種方式進一步精簡代碼,寫出像下面這樣 Dart 風味十足的代碼:

class Singleton {
  static Singleton _instance;

  Singleton._internal() {
    _instance = this;
  }

  factory Singleton() => _instance ?? Singleton._internal();
}

這裏,使用 ?? 做爲 _instance 實例的判空操做符,若是爲空則調用構造函數實例化不然直接返回,也能夠達到單例的效果。

以上,Dart 單例中懶加載的無不是使用判空來實現的(if (_instance == null)??),可是在 Dart 空安全特性裏還有一個很是重要的操做符 late ,它在語言層面就實現了實例的懶加載,以下面這個例子:

class Singleton {
  Singleton._internal();
  
  factory Singleton() => _instance;
  
  static late final Singleton _instance = Singleton._internal();
}

被標記爲 late 的變量 _instance 的初始化操做將會延遲到字段首次被訪問時執行,而不是在類加載時就初始化。這樣,Dart 語言特有的單例模式的實現方式就這麼產生了。

Flutter 化

說到工廠構造函數/空安全操做符等 Dart 語法上的特性,Flutter 應用中的例子已經家常便飯了, 但光看單例模式的定義,咱們還必須聯想到 Flutter 中另外一個很是重要的 widget,那就是 InheritedWidget。

若是你已是一個 Flutter 小能手,或者已經看過《Flutter 開發之旅從南到北》和以前的文章的話,必定已經對他的做用有了清晰的認識了。

InheritedWidget 狀態可遺傳的特性能夠幫助咱們很方便的實現父子組件之間的數據傳遞,同時,它也能夠做爲狀態管理中的 數據倉庫,做爲整個應用的數據狀態統一保存的地方。

圖源《Flutter 開發之旅從南到北》—— 第九章 圖 9.4

上面代碼中,咱們經過繼承 InheritedWidget 就實現了本身的可遺傳組件 _InheritedStateContainer,其中的 data 變量表示全局狀態數據,在這裏就能夠被認爲是整個應用的一個單例對象

_InheritedStateContainer 還接受 child 參數做爲它的子組件,child 表示的因此子組件們就都可以以某種方式獲得 data 這個單一的全局數據了。

約定俗成地,Flutter 源碼常常會提供一些 of 方法(類比 getInstance())做爲幫助咱們拿到全局數據的輔助函數。

以 Flutter 中典型的 Theme 對象爲例。咱們一般會在應用的根組件 MaterialApp 中建立 ThemeData 對象做爲應用統一的主題樣式對象:

MaterialApp(
  title: 'Flutter Demo',
  theme: ThemeData(
    primarySwatch: Colors.blue,
    visualDensity: VisualDensity.adaptivePlatformDensity,
  ),
  home: MyHomePage(title: 'Flutter Demo Home Page'),
);

在其餘任意的組件中,咱們可使用 Theme.of(context) 拿到該對象了,且這個對象全局惟一。以下所示,咱們能夠將該 ThemeData 對象中的 primaryColor 應用在 Text 中:

// 使用全局文本樣式
Text(
  'Flutter',
  style: TextStyle(color: Theme.of(context).primaryColor),
)

這個角度來看,InheritedWidget 徹底能夠被咱們看做是最原生、最 Flutter 的單例應用了。

本文小結

本篇文章,咱們經歷了從實現普通單例到應用 getter 操做符 的 Dart 單例,到使用 工廠構造函數 Dart 單例,再到使用了 工廠構造函數 + 空安全語法 + 箭頭函數 的 Dart 單例,最後結合對 InheritedWidget 概念的理解,看到了 Flutter 中特有的單例模式,算是每一步都走了一遍。但學習設計模式的重點仍是在於實際應用,但願你們從此在實際工程中能將這些概念用起來,若是你想更進一步理解 Dart 中的單例模式,能夠參閱「拓展閱讀」學習更多,但願對你有幫助。

單例模式從南到北

拓展閱讀

關於本系列文章

Flutter / Dart 設計模式從南到北(簡稱 Flutter 設計模式)系列內容預計兩週發佈一篇,着重向開發者介紹 Flutter 應用開發中常見的設計模式以及開發方式,旨在推動 Flutter / Dart 語言特性的普及,以及幫助開發者更高效地開發出高質量、可維護的 Flutter 應用。

相關文章
相關標籤/搜索