文/ 杨加康,CFUG 社区成员,《Flutter 开发之旅从南到北》作者,小米工程师

单例设计模式(Singleton Design Pattern)理解起来非常简单。

一个类只允许创建一个实例,那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。

作为最简单的一种设计模式之一,对于单例本身的概念,大家一看就能明白,但在某些情况下也很容易使用不恰当。相比其他语言,Dart 和 Flutter 中的单例模式也不尽相同,本篇文章我们就一起探究看看它在 Dart 和 Flutter 中的应用。

Flutter(able) 的单例模式

一般来说,要在代码中使用单例模式,结构上会有下面这些约定俗成的要求:

  • 单例类(Singleton)中包含一个引用自身类的静态属性实例(instance),且能自行创建这个实例。
  • 该实例只能通过静态方法 getInstance() 访问。
  • 类构造函数通常没有参数,且被标记为私有,确保不能从类外部实例化该类。

遵循以上这些要求,我们就不难能用 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 状态可遗传的特性可以帮助我们很方便的实现父子组件之间的数据传递,同时,它也可以作为状态管理中的 数据仓库,作为整个应用的数据状态统一保存的地方。

上面代码中,我们通过继承 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 应用。

Flutter(able) 的单例模式的更多相关文章

  1. Flutter日常笔记

    factory修饰的构造方法 表示不是每次返回的都是新创建出来的对象, 可以取内存中已有的, 比如单例模式的书写 每次返回的都是一个实例, 这时要使用factory修饰构造方法 flutter不要求显 ...

  2. flutter dio网络请求封装实现

    flutter dio网络请求封装实现 文章友情链接:   https://juejin.im/post/6844904098643312648 在Flutter项目中使用网络请求的方式大致可分为两种 ...

  3. 【Flutter 实战】大量复杂数据持久化

    老孟导读:上一篇文章讲解了 Android 和 iOS 的文件目录系统,此篇文章讲解如何使用 SQLite 保存数据. 欢迎大家投稿:http://laomengit.com/plan/Contrib ...

  4. Flutter 设计模式|工厂模式家族

    文/ 杨加康,CFUG 社区成员,<Flutter 开发之旅从南到北>作者,小米工程师 在围绕设计模式的话题中,工厂这个词频繁出现,从 简单工厂 模式到 工厂方法 模式,再到 抽象工厂 模 ...

  5. C++实现线程安全的单例模式

    在某些应用环境下面,一个类只允许有一个实例,这就是著名的单例模式.单例模式分为懒汉模式,跟饿汉模式两种. 首先给出饿汉模式的实现 template <class T> class sing ...

  6. 23种设计模式--单例模式-Singleton

    一.单例模式的介绍 单例模式简单说就是掌握系统的至高点,在程序中只实例化一次,这样就是单例模式,在系统比如说你是该系统的登录的第多少人,还有数据库的连接池等地方会使用,单例模式是最简单,最常用的模式之 ...

  7. angular2系列教程(十)两种启动方法、两个路由服务、引用类型和单例模式的妙用

    今天我们要讲的是ng2的路由系统. 例子

  8. java设计模式之--单例模式

    前言:最近看完<java多线程编程核心技术>一书后,对第六章的单例模式和多线程这章颇有兴趣,我知道我看完书还是记不住多少的,写篇博客记录自己所学的只是还是很有必要的,学习贵在坚持. 单例模 ...

  9. 设计模式C#合集--单例模式

    单例模式 代码: 第一种: private static Singleton singleton = null; private Singleton() { } public static Singl ...

  10. 设计模式之单例模式(Singleton)

    设计模式之单例模式(Singleton) 设计模式是前辈的一些经验总结之后的精髓,学习设计模式可以针对不同的问题给出更加优雅的解答 单例模式可分为俩种:懒汉模式和饿汉模式.俩种模式分别有不同的优势和缺 ...

随机推荐

  1. .NET Core 3.x 基于AspectCore实现AOP,实现事务、缓存拦截器

    最近想给我的框架加一种功能,就是比如给一个方法加一个事务的特性Attribute,那这个方法就会启用事务处理.给一个方法加一个缓存特性,那这个方法就会进行缓存.这个也是网上说的面向切面编程AOP. A ...

  2. Activity的创建

    Activity的创建: 1.layout内写入相关代码 此处为显示的页面 2.Java内创建相关类写入代码 3.在清单内写入 快捷方法:直接完成上面步骤 layout: match_parent// ...

  3. 写写stream流的终结操作

    终结操作和中间操作的区别:中间操作返回的一直都是stream,所以可以一直使用,但是终结操作返回的不是stream,后面不能继续操作 foreach:对流中的所有元素进行遍历操作 count:获取当前 ...

  4. 我出一道面试题,看看你能拿 3k 还是 30k!

    大家好,我是程序员鱼皮.欢迎屏幕前的各位来到今天的模拟面试现场,接下来我会出一道经典的后端面试题,你只需要进行 4 个简单的选择,就能判断出来你的水平是新手(3k).初级(10k).中级(15k)还是 ...

  5. sqoop 从数据库导入数据到hdfs

    前提 配置hadoop配置文件 前提 启动hadoop 配置hive 改名进入sqoop/conf 增加环境变量 tar xf sqoop-1.4.7.bin__hadoop-2.6.0.tar.gz ...

  6. Python异常处理try+except用法

    1.except是用来捕获程序异常的 异常代码如: ModuleNotFoundError(没有找到模块,安装提示的模块即可) AttributeError(没有访问属性) TypeError(类型错 ...

  7. 使用 useState 管理响应式状态

    title: 使用 useState 管理响应式状态 date: 2024/8/1 updated: 2024/8/1 author: cmdragon excerpt: 摘要:本文详细介绍了在Nux ...

  8. 《A Palestinian Woman Embraces the Body of Her Niece》—— 4月19日报道 2024年世界新闻摄影大赛结果在荷兰出炉,一张巴勒斯坦妇女在加沙地带抱着被杀害的五岁侄女尸体的照片被评为年度最佳作品

    The genocide is not just a matter between the parties involved; it's a concern for all humanity. Gen ...

  9. 中国AI领域超越美国的拐点在哪 —— 国产AI芯片量产化的成本接近于美国成熟AI芯片的成本

    作为AI领域的一个大头兵,本是没有资格去谈论high level层面的东西的,只不过总有些忍不得说的事情. 今天这里就说下个人对中国AI发展的一个观点或是预测,在我看来中国AI领域超越美国的拐点就在于 ...

  10. 键盘中上、下、左、右四个光标键所对应的ASCII码值为多少

    首先给出ASCII码值表: 上.下.左.右这四个光标键对应的ASCII码值不是一个值而是三个,准确的说光标键的ASCII码值是一个组合. 每个方向键所对应的三个键值为:0x1b + 0x5b + n ...