by Yang Jiakang, CFUG community member, author of "Flutter Development Journey from South to North", Xiaomi engineer
(Singleton Design Pattern) is very simple to understand.
A class is only allowed to create one instance, then this class is a singleton class. This design pattern is called the singleton design pattern, or the singleton pattern for short.
As one of the simplest design patterns, the concept of the singleton itself can be understood at a glance, but in some cases it is also easy to use inappropriately. Compared with other languages, the singleton mode in Dart and Flutter is different. In this article, we will explore its application in Dart and Flutter together.
Flutter(able) singleton mode
Generally speaking, to use the singleton pattern in the code, there will be the following conventional requirements in structure:
- The Singleton contains a static property instance that refers to its own class, and can create this instance by itself.
- This instance can only be accessed through the static method
getInstance()
. - The class constructor usually has no parameters and is marked as private, ensuring that the class cannot be instantiated from outside the class.
Following these requirements, it is not difficult for us to write a common singleton pattern with Dart:
class Singleton {
static Singleton _instance;
// 私有的命名构造函数
Singleton._internal();
static Singleton getInstance() {
if (_instance == null) {
_instance = Singleton._internal();
}
return _instance;
}
}
At the same time, when implementing the singleton mode, you also need to consider the following points to prevent problems during use:
- Do you need lazy loading, that is, class instances are only created when they are needed for the first time.
- Whether it is thread safe or not, the concurrency of multithreading needs to be considered in multithreaded languages such as Java and C++. Since Dart is a language with a single-threaded model, all codes usually run in the same isolate, so there is no need to consider thread safety issues.
- In some cases, the singleton pattern will be considered a anti-pattern , because it violates the single responsibility principle in the SOLID principle, the singleton class itself controls its own creation and life cycle, and the singleton pattern is general Without an interface, expansion is difficult.
- The use of singleton mode will affect the testability of the code. If the singleton class relies on a relatively heavy external resource, such as DB, when we write unit tests, we hope to replace it by mocking. The hard-coded use of singleton classes makes it impossible to implement mock replacement.
In the actual coding process, common applications of singleton mode are:
- The Logger class of the global log, the application global configuration data object class, and the single business management class.
- It takes more resources when creating an instance, or a class that takes a long time to instantiate.
- and many more...
Dartization
As mentioned above, the Dart language is the language of the single-threaded model. When implementing the singleton mode, we can no longer consider the thread safety . Many other features of Dart can still help us achieve a more Dartized singleton.
Using the getter operator, you can break the established singleton mode, you must write a getInstance()
static method rule, simplify the template code we must write, as follows get instance
:
class Singleton {
static Singleton _instance;
static get instance {
if (_instance == null) {
_instance = Singleton._internal();
}
return _instance;
}
Singleton._internal();
}
The use of Dart's getter is roughly the same as that of ordinary methods, except that the caller no longer needs to use parentheses, so that we can directly use the following method to get this singleton object when using it:
final singleton = Singleton.instance;
And the unique factory constructor (factory constructor) in feature that 16117cf68361d9 does not have to create a new class instance every time. Using this feature, we can write a more elegant Dart(able ) Singleton mode, as follows:
class Singleton {
static Singleton _instance;
Singleton._internal();
// 工厂构造函数
factory Singleton() {
if (_instance == null) {
_instance = Singleton._internal();
}
return _instance;
}
}
Here we no longer use the getter operator to provide an additional function, but hand the generation of the singleton object to the factory constructor. At this time, the factory constructor only creates _instance
when it is needed for the first time, and returns every time thereafter. The same instance. At this time, we can use the ordinary constructor to get the singleton as follows:
final singleton = Singleton();
If you also master Dart's features such as empty safety and arrow functions, you can also use another way to further streamline the code and write Dart-flavored code like the following:
class Singleton {
static Singleton _instance;
Singleton._internal() {
_instance = this;
}
factory Singleton() => _instance ?? Singleton._internal();
}
Here, use ??
as the _instance
instance. If it is null, call the constructor to instantiate it, otherwise return directly, which can also achieve the effect of a singleton.
Above, the lazy loading in the Dart singleton is not realized by using the empty space ( if (_instance == null)
or ??
), but there is also a very important operator late
in the Dart empty safety feature, which realizes the laziness of the instance at the language level. Load, as in the following example:
class Singleton {
Singleton._internal();
factory Singleton() => _instance;
static late final Singleton _instance = Singleton._internal();
}
The initialization of the variable _instance
marked as late
will be delayed until the field is accessed for the first time, instead of being initialized when the class is loaded. In this way, the realization of the unique singleton pattern of the Dart language was born.
Flutterization
Speaking of Dart grammatical features such as factory constructors/null safety operators, examples in Flutter applications are not uncommon, but looking at the definition of the singleton pattern, we must also think of another very important widget in Flutter, that It is InheritedWidget.
If you are already a small Flutter expert, or have read "Flutter Development Journey from South to North" and the previous articles, you must have a clear understanding of his role.
The inheritable characteristics of the InheritedWidget state can help us easily realize the data transfer between the parent and child components. At the same time, it can also be used as the data warehouse in the state management, as the place where the data state of the entire application is stored uniformly.
In the above code, we implemented our own inheritable component _InheritedStateContainer
by inheriting InheritedWidget. The data
variable represents the global state data. can be considered as a singleton object for the entire application.
_InheritedStateContainer
also accepts the child
parameter as its subcomponent, so the child
represented by 06117cf6836327 can get the single global data of data
in some way.
Conventionally, the Flutter source code often provides some of
methods (analog getInstance()
) as auxiliary functions to help us get global data.
Take the typical Theme object in Flutter as an example. We usually create the ThemeData
object in the root component MaterialApp
of the application as the uniform theme style object of the application:
MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
In any other component, we can use Theme.of(context)
to get the object, and this object is globally unique. As shown below, we can the ThemeData
object primaryColor
Application Text
of:
// 使用全局文本样式
Text(
'Flutter',
style: TextStyle(color: Theme.of(context).primaryColor),
)
From this perspective, InheritedWidget can be regarded as the most native and most Flutter singleton application.
Summary of this article
In this article, we have gone through the implementation of the ordinary singleton to the Dart singleton getter operator factory constructor Dart singleton, and then to the factory constructor + empty safe syntax + arrow function , finally combined with the understanding of the concept of InheritedWidget, I saw the unique singleton mode in Flutter, and I went through every step. But the focus of learning design patterns lies in practical applications. I hope you can use these concepts in practical projects in the future. If you want to understand the singleton pattern in Dart further, you can refer to " Extended Reading " to learn more. Hope it helps you.
Further reading
- Book "Flutter Development Journey from South to North" —— Chapter 2, Chapter 9
- Singleton mode
- Dart Air Safety
- Delayed initialization
About this series of articles
The Flutter / Dart design pattern from south to north (referred to as the Flutter design pattern) series of content is expected to be published in two weeks, focusing on introducing developers to common design patterns and development methods in Flutter application development, aiming to promote the development of Flutter / Dart language features Popularize, and help developers to develop high-quality, maintainable Flutter applications more efficiently.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。