文/ 楊加康,CFUG 社區成員,《Flutter 開發之旅從南到北》做者,小米工程師html
單例設計模式(Singleton Design Pattern)理解起來很是簡單。segmentfault
一個類只容許建立一個實例,那這個類就是一個單例類,這種設計模式就叫做單例設計模式,簡稱單例模式。
做爲最簡單的一種設計模式之一,對於單例自己的概念,你們一看就能明白,但在某些狀況下也很容易使用不恰當。相比其餘語言,Dart 和 Flutter 中的單例模式也不盡相同,本篇文章咱們就一塊兒探究看看它在 Dart 和 Flutter 中的應用。設計模式
通常來講,要在代碼中使用單例模式,結構上會有下面這些約定俗成的要求:安全
getInstance()
訪問。遵循以上這些要求,咱們就不難能用 Dart 寫出一個普通的單例模式:多線程
class Singleton { static Singleton _instance; // 私有的命名構造函數 Singleton._internal(); static Singleton getInstance() { if (_instance == null) { _instance = Singleton._internal(); } return _instance; } }
同時,在實現單例模式時,也須要考慮以下幾點,以防在使用過程當中出現問題:併發
在實際編碼過程當中,單例模式常見應用有:函數
如上文所說的,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 語言特有的單例模式的實現方式就這麼產生了。
說到工廠構造函數/空安全操做符等 Dart 語法上的特性,Flutter 應用中的例子已經家常便飯了, 但光看單例模式的定義,咱們還必須聯想到 Flutter 中另外一個很是重要的 widget,那就是 InheritedWidget。
若是你已是一個 Flutter 小能手,或者已經看過《Flutter 開發之旅從南到北》和以前的文章的話,必定已經對他的做用有了清晰的認識了。
InheritedWidget 狀態可遺傳的特性能夠幫助咱們很方便的實現父子組件之間的數據傳遞,同時,它也能夠做爲狀態管理中的 數據倉庫,做爲整個應用的數據狀態統一保存的地方。
上面代碼中,咱們經過繼承 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 應用。