Flutter设计模式全面解析:单例模式

在这里插入图片描述
谈到设计模式这个“古老”的话题,大家先别急着划走哈,虽然对它再熟悉不过,几乎是最初开始学习编程到现在伴随着我们整个编程生涯,最早 JavaC++ 语言实现的各种设计模式到现在还会经常有所接触,面试中也是必问的环节,在开发 Flutter 项目的时候,也会多少借鉴了其它语言设计模式的实现,但始终觉得dart 语言实现的设计模式理解不够系统,有的实现还缺点儿 dart 语言本身的语法特性。加上最近在看一些 Flutter 框架及常用第三方插件的源码时候,发现这些源码背后或多或少都有设计模式的影子。铺垫了这么多,还真不是我在这里故意卷Flutter 设计模式这些个话题,它对于我们日常编写高质量代码及理解 dart 语言特性、Flutter 的框架和热门第三方插件、OOP 设计模式、理解 SOLID 原则及其应用、代码架构或软件工程等还是有很大的帮助的。既然这么多的好处,那还等什么呢?

Singleton Pattern

Singleton 模式在项目中再常见不过了,实现起来也很简单,它一般包括私有构的造函数、一个静态实例和提供全局访问点。该模式是用来确保一个类只有一个实例,并提供一个全局访问点。简单来说,就是限制一个类在应用程序中只能有一个实例存在。这种模式通常用于需要全局共享资源的场景,比如配置管理、日志记录器、全局状态保存等,下面来实现一个单例类。

class Singleton {// 1. 私有构造函数Singleton._privateConstructor();// 2. 静态实例static final Singleton _instance = Singleton._privateConstructor();// 3. 提供全局访问点static Singleton get instance => _instance;
}

上面的单例类 Singleton 可以看出:

  1. Singleton 类构造函数被标记为私有,用来确保该类不能从类外部去实例化。
  2. 包含一个静态实例,该实例是对类实例本身的引用。
  3. 该实例只能通过静态的 get 访问,为全局提供访问点。

除了上面的写法还有没有其它的实现呢?我们可以使用 factory 构造函数特性来实现。

class Singleton {static final Singleton _instance = Singleton._internal();factory Singleton() {return _instance;}Singleton._internal();
}// 调用
// Singleton();

Dart 中,factory 构造函数是一种特殊的构造函数,用于控制类实例的创建过程。与常规构造函数不同,factory 构造函数并不总是创建一个新的实例,它可以返回现有的实例或一个子类的实例,factory 构造函数也常被用来实现单例模式。当然除了前面两种还有如下面这种更加简单的实现:

class Singleton {Singleton._();static final Singleton instance = Singleton._();
}// 调用
// Singleton.instance;

Flutter 开发中,基于 factory 构造函数和上面第三种实现方式会更常见,因为它们够简单直接且线程安全。那么在 Dart 中还有没有更加便捷的方式创建单例呢?当然有的。

其它的实现方式

通过依赖注入插件 injectable 添加为类 @Singleton@LazySingleton 注解也能实现单例,代码也更加的简洁,也是我个人比较推荐的实现方式。

abstract class AppNavigator {const AppNavigator();void push();
}(as: AppNavigator)
class AppNavigatorImpl {void push(){//......}
}

线程安全

Flutter 是否真的有必要像上面例子中通过加锁来保证线程安全呢?我们知道 Dart 可以说是一种单线程编程语言,代码的执行通常发生在一个单线程上。这个单线程模型是通过事件循环来管理的。事件循环负责处理事件队列中的任务,这些任务包括 I/O 操作、定时器回调、用户输入等。

所有的 Dart 代码(除非明确使用多线程技术)都是在这个单线程上执行的,也就是一个隔离区( isolate )中执行,因此,在 Dart 中实现单例时,只要您不自己创建一个新的独立于代码的隔离区( isolate ),根本就不必担心线程安全性。所以上面懒加载式单例的第一种实现方式基本上能满足我们的需求。

单例模式与 SOLID 原则

单例模式由于其本身的实现(确保一个类只有一个实例,并提供全局访问点)在某些方面与 SOLID 原则(面向对象设计的五个原则)是相冲突的,下面实现一个简单的日志打印的单例类来详细说明一下。

class Logger {static final Logger _instance = Logger._internal();// 私有构造函数,防止外部实例化Logger._internal();static Logger get instance => _instance;void log(String message) {print("Log: $message");}
}

SOLID 原则中的单一职责原则要求每个类应该只有一个职责,即仅负责一件事。而 Logger 单例类不仅负责日志记录,还负责管理其唯一实例的生命周期,它承担了额外的职责,违背了单一职责原则。

SOLID 原则中开闭原则要求类应该对扩展开放,对修改关闭,在而单例 Logger 中如果想要拓展以支持不同的日志目标,如将日志写入文件等,不得不修改现有代码,而不是通过继承或组合进行扩展功能。

class Logger {static final Logger _instance = Logger._internal();Logger._internal();static Logger get instance => _instance;void log(String message) {print("Log: $message");}// 添加新功能void saveLogToFile(String message) {// 将日志写入文件的代码}
}

这不符合开闭原则,因为需要直接修改 Logger 类来添加新功能。

里氏替换原则要求子类应该可以替换父类,并且不影响其它代码的正确执行。单例模式通过私有构造函数限制实例化,所以继承和替换就很难做到了。例如,如果创建一个子类 FileLogger 继承自 Logger

class FileLogger extends Logger {void log(String message) {// 自定义文件日志记录逻辑}
}

上面写法会直接报错,FileLogger 也没法替换 Logger 来实现文件日志记录的逻辑。

接口隔离原则要求不依赖于不需要的接口。单例模式本身与接口隔离原则没有直接冲突。然而,如果单例类实现了过多的职责,就可能导致其接口庞大,调用方很多时候不得不依赖于它们不需要的方法,这就违反接口隔离原则。

class Logger {static final Logger _instance = Logger._internal();Logger._internal();static Logger get instance => _instance;void log(String message) {print("Log: $message");}void logToFile(String message) {// 将日志写入文件的代码}void logToNetwork(String message) {// 将日志发送到网络服务器的代码}
}

依赖倒置原则要求高层模块不应该依赖低层模块,二者都应该依赖于抽象。单例模式通常通过静态方法或属性提供实例,这使得高层模块依赖于具体实现,而不是抽象接口。这个原则我在之前的文章《Flutter大型项目架构:依赖管理篇》中有讲到,文章的 AppNavigatorAppNavigatorImpl 类,AppNavigator 是抽象类,所有用到路由的调用都是通过 AppNavigator,而 AppNavigatorImpl 才是实现类,也可以参考上面其它实现方式的代码。

需要注意什么

虽说 Singleton 很多和 SOLID 原则相违背,但其简单直接实现方式,尤其是在需要全局共享资源的场景中去使用太方便了。但是我们在追求方便的同时也要留意过度使用 Singleton 模式可能带来的问题,尤其是大型的 Flutter 项目中。

  1. 确保单例适当的生命周期,避免资源的泄露,某些时候单例对象可能会持有大量资源,或者与其他部分有复杂的交互,需要在合适的时机释放这些资源。
  2. 单例模式应仅用于那些需要在全局范围内唯一且易于访问的对象,如 Logger 类、AppNavigator 类等。如果滥用单例会导致代码难以维护和测试。
  3. 确保单例对象在使用前已经正确配置和初始化。特别是在大型项目中,单例可能需要依赖多个模块的初始化顺序,确保这些依赖关系不会引发初始化错误,如在一个统一的模块(initializer)来处理 Singleton 初始化。
  4. 在类中直接单例不咋容易被测试,这个时候可以使用依赖注入(DI)来创建和管理单例实例,在测试时可以替换单例对象。
  5. 确保单例对象的职责单一,不要让其承担过多的责任。通过接口分离和依赖注入,保持系统设计的灵活性和可扩展性。参考 AppNavigatorAppNavigatorImpl 类的实现。

小结

本文介绍了单例模式实现的几种方式、单例的线程安全问题、单例模式与 SOLID 原则和在大型项目中使用单例需要注意什么,希望对你在以后的 Flutter 开发过程中有所帮助,感谢您的阅读!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/14898.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Adobe Camera Raw 11 for Mac/win:摄影后期处理的革命性飞跃

在数字摄影的世界里,RAW格式以其未压缩的原始数据特性,为摄影师提供了更大的后期处理空间。而Adobe Camera Raw 11,作为这一领域的翘楚,以其卓越的性能和创新的功能,为摄影师们带来了前所未有的创作体验。 Adobe Came…

LeetCode450删除二叉搜索树中的节点

题目描述 给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。一般来说,删除节点可分为两个步骤&#xff1…

Linux环境中部署docker私有仓库Registry与远程访问详细流程

目录 前言 1. 部署Docker Registry 2. 本地测试推送镜像 3. Linux 安装cpolar 4. 配置Docker Registry公网访问地址 5. 公网远程推送Docker Registry 6. 固定Docker Registry公网地址 前言 作者简介: 懒大王敲代码,计算机专业应届生 今天给大家聊…

网络安全知识核心20要点

1、什么是SQL注入攻击 概述 攻击者在 HTTP 请求中注入恶意的 SQL 代码,服务器使用参数构建数据库 SQL 命令时,恶意SQL 被一起构造,并在数据库中执行。 注入方法 用户登录,输入用户名 lianggzone,密码‘ or ‘1’’…

揭秘Python的魔法:装饰器的超能力大揭秘 ‍♂️✨

文章目录 Python进阶之装饰器详解1. 引言装饰器的概念与意义装饰器在Python编程中的作用 2. 背景介绍2.1 函数作为对象2.2 高阶函数 3. 装饰器基础3.1 理解装饰器3.2 装饰器的工作原理 4. 带参数的装饰器4.1 为什么需要带参数4.2 实现带参数的装饰器使用函数包裹装饰器使用类实…

TypeScript-泛型

泛型(Generics) 指在定义接口,函数等类型的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性,使用泛型可以复用类型并且让类型更加灵活 泛型接口-interface 语法:在 interface 接口类型的名称后面使用…

AI服务器连接解决方案领军企业Astera Labs宣布,将在台湾组建首个位于硅谷以外的Cloud-Scale Interop Lab

AI服务器连接解决方案领军企业Astera Labs宣布,将在台湾组建首个位于硅谷以外的Cloud-Scale Interop Lab,集结中国台湾制造商共同参与。据《工商时报》报道,Astera Labs将与台湾主要ODM客户紧密合作,预期广达、英业达、纬创资通、…

web前端的路径和Servlet注解开发

目录 在web前端的两种路径 绝对路径的两种写法 相对路径 相对路径进阶 使用注解开发Servlet 使用注解开发Servlet的注意事项 使用idea创建servlet模板 在web前端的两种路径 绝对路径的两种写法 1.带网络三要素 http://ip地址:端口号/资源路径 2.不带网络三要素 /资源路…

『哈哥赠书 - 53期』-『深入浅出 Spring Boot 3.x』

⭐️ 《深入浅出 Spring Boot 3.x》 ⭐️ 学习Spring Boot的必读之书 在 Java 后端开发领域,功能强大的 Spring 开源框架不仅是首选,也是事实上的标准。但由于 Spring 存在配置烦琐、部署不易、依赖管理困难等问题,因此基于 Spring 的快速开…

告别传统,拥抱未来——上门回收小程序引领变革

随着科技的飞速发展,我们生活的方方面面都在经历着前所未有的变革。在环保和可持续发展的背景下,传统的废品回收方式已经难以满足现代社会的需求。而上门回收小程序的出现,正以其便捷、高效的特点,引领着废品回收行业的变革。 一、…

【NLPl练习】Transformer起源与发展

Transformer总结 Transformer模型类别分为: 纯Encoder模型纯Decoder模型Encoder-Decoder模型 Transformer模型的本质是预训练语言模型,首先采用自监督学习的方式在大量生语料库上训练,无需人工标注。常用的预训练任务主要有以下两个&#xff…

力扣343 整数拆分 Java版本

文章目录 题目描述代码 题目描述 给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k > 2 ),并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 示例 1: 输入: n 2 输出: 1 解释: 2 1 1, 1 1 1。 示例 2: 输…

Leedcode69:x的平方根_Java解法

Problem: 69. x 的平方根 题目描述思路解题方法复杂度Code 题目描述 给你一个非负整数 x ,计算并返回 x 的 算术平方根 。 由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。 注意:不允许使用任何内置指数函数和…

HCIP-Datacom-ARST自选题库__OSPF单选【80道题】

1.OSPFV2是运行在IPV4网络的IGP,OSPFV3是运行在IPV6网络的ICP,OSPFV3与OSPFv2的报文类型相同,包括Hello报文、DD报文、LSR报文、LSU报文和LSAck报文。关于OSPFv3报文,以下哪个说法是正确的 OSPFv3使用报文头部的认证字段完成报文…

NVIDIA Jetson AGX Orin虚拟显示器安装

NVIDIA Jetson AGX Orin虚拟显示器安装 ​ 在orin上使用过程中。由于没有连接显示屏导致无法正常使用远程桌面工具进行代码调试。可使用虚拟显示器解决上述问题。 1、安装远程桌面软件并进行相关配置 ​ 安装远程桌面软件参考各个远程桌面软件官网介绍。 2、开启界面共享 …

【LeetCode】数组——双指针法

1 双指针法 1.1 介绍 双指针法是一种常用的算法技巧,通常用于处理数组或链表中的问题。它使用两个指针,通常一个从数组的开始位置遍历,另一个从数组的末尾位置开始遍历,根据问题的不同,这两个指针可以同时移动&#…

oracle rownum分页出现重复数据

oracle rownum分页出现重复数据的情况: 一般情况是分组的字段不唯一导致的 解决办法: 原始sql:(错误的) SELECT *FROM (SELECT a.*, ROWNUM rFROM hospital_inpatient_medication aWHERE TO_CHAR(discharge_date,YYYY-MM-DD HH24:MI:SS) BETWEEN 20…

抖音跳转微信卡片制作教程 小白也能搞

实测可以正常跳转,很牛逼,给大家分享一下~ 这是我做出来抖音发出去的效果,大家会制作了可以去卖钱,市场上一个这个卡片都要卖50-200,很不错的!! https://pan.baidu.com/s/1xPmGAWPcbAp7eXg7Dc…

温故而知新-秒杀项目篇【面试复习】

温故而知新-秒杀项目篇【面试复习】 前言版权推荐温故而知新-论坛项目篇【面试】秒杀项目中注册模块怎么实现的?秒杀项目中登录模块怎么实现的?秒杀项目中显示登录用户信息怎么实现的?SessionStorage是什么?为什么不用session而用token什么是…

骨位深间距小模具镶件如何走水路?3D打印让一切简单

在模具制造领域,骨位深且间距小的模具镶件由于结构复杂,传统加工方法难以制造出符合要求的冷却水路,导致模具在注塑过程中容易产生热量积聚,进而引发烫伤、缩孔等不良。然而,随着3D打印技术的飞速发展,这些…