单例模式 (Singleton) - 你的Flutter App里那个“独一份儿”的家伙
想象一下这个场景:
你开了一家超级火爆的奶茶店 🧋。店里有一台核心设备——唯一的收银机💰。这个收银机非常重要:
- 整个店只有一台: 你不能让每个店员自己带一台收银机,那样账就全乱了。
- 谁都需要用它: 点单员要记录订单,店长要查账,财务要结算,都得找这台机器。
- 随时能访问: 当顾客点单时,点单员必须能立刻找到并使用这台收银机。
在Flutter世界里,这个“唯一的收银机”就是“单例模式”!
单例模式是啥?一句话:
保证一个类在整个App运行期间,永远只有一个实例(对象),并且提供一个全局访问点,让任何地方的代码都能方便地拿到这个唯一的实例。
为啥在Flutter里需要它?
Flutter App 里有很多东西,你真心希望它只有一个,并且到处都能安全、方便地访问:
- 全局配置管理器: 存储 App 的主题颜色、语言设置、API 地址等。你肯定不希望不同的页面读到的主题颜色不一样吧?
- 缓存管理器: 管理内存中的图片缓存、数据缓存。多个缓存实例会导致内存浪费和数据不一致。
- 日志记录器: 所有地方的日志都应该汇集到同一个记录器里。
- 数据库连接: 通常,打开多个数据库连接是低效且容易出错的。
- 状态管理(部分场景): 当某些全局状态(比如用户登录信息)需要被非常多的组件访问,且严格保证一致性时,单例可以作为简单方案(虽然复杂状态更推荐Provider/Riverpod/BLoC等)。
Flutter中的经典例子:MaterialApp / CupertinoApp
其实你每天都在用单例!看看你的 main.dart:
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp( // <--- 看这里!
title: '我的Flutter App',
theme: ThemeData(primarySwatch: Colors.blue),
home: MyHomePage(),
);
}
}
MaterialApp 这个组件在创建时,内部就悄悄地帮我们建立了一个非常重要的单例:Navigator 和 ScaffoldMessenger 的根实例。
Navigator.of(context): 当你调用这个方法时,Flutter 会沿着context向上查找,找到离你最近的那个MaterialApp(或CupertinoApp) 创建的Navigator单例对象。整个App通常只有一个顶层的Navigator来管理页面堆栈(导航),这就是单例的典型应用!你不需要自己new Navigator(),框架保证你通过of(context)拿到的是正确的、唯一的那个。ScaffoldMessenger.of(context).showSnackBar(...): 同样道理,这背后也是一个单例在管理SnackBar的显示。
动手!用单例模式实现一个“购物车”
假设我们要一个全局的、唯一的购物车管理器 CartManager:
class CartManager {
// 1. 私有静态实例变量,存储唯一的实例
static final CartManager _instance = CartManager._internal();
// 2. 私有的命名构造函数(防止外部随意 `new CartManager()`)
CartManager._internal() {
// 这里可以进行一些初始化工作
_items = []; // 初始化一个空购物车
print('购物车经理诞生了!仅此一位。');
}
// 3. 公共的、静态的访问点(全局访问点)
static CartManager get instance => _instance;
// 购物车里的商品 (私有变量,通过方法操作)
List<String> _items = [];
// 添加商品
void addItem(String item) {
_items.add(item);
print('添加了: $item');
}
// 查看购物车
List<String> get items => List.unmodifiable(_items); // 返回不可修改的副本,保护内部数据
// 清空购物车
void clearCart() {
_items.clear();
print('购物车已清空!');
}
}
关键点解析:
-
static final CartManager _instance = CartManager._internal();:static: 这个变量属于CartManager类本身,而不是类的某个具体对象。它在程序加载时就初始化了。final: 确保_instance一旦被赋值(指向那个唯一的CartManager对象),就再也不能指向其他对象了。CartManager._internal(): 调用私有的构造函数来创建这个唯一的实例。
-
CartManager._internal(): 这是一个私有的命名构造函数。名字前面的下划线_表示它是私有的。外部代码无法直接调用new CartManager._internal()来创建新实例,这就从语法上堵死了创建多个实例的可能。 -
static CartManager get instance => _instance;:- 这是一个静态的 getter 方法。
- 外部代码想访问购物车管理器,只能通过
CartManager.instance这个唯一的入口。 - 它返回的就是我们在第1步创建好的那个
_instance。
如何使用这个单例购物车?
// 在任意一个Widget的方法里,比如按钮点击事件:
void _addToCartButtonPressed() {
// 通过全局访问点获取唯一的 CartManager 实例
CartManager cartManager = CartManager.instance;
// 使用它!
cartManager.addItem('新款手机');
print('当前购物车: ${cartManager.items}');
}
// 在另一个完全不同的地方(比如支付页面):
void _checkout() {
CartManager.instance.clearCart(); // 直接访问同一个实例清空购物车
}
运行效果:
无论你在 App 的哪个角落调用 CartManager.instance,你拿到的都是同一个 CartManager 对象。你添加商品、清空购物车,操作的都是这“独一份儿”的购物车。日志里也只会打印一次“购物车经理诞生了!”。
总结:单例模式就是你的“独一份儿”管家
- 核心目标: 一个类,只有一个实例,全局都能访问。
- 关键实现:
- 私有静态变量持有唯一实例。
- 私有构造函数(防止外部
new)。 - 公共静态方法/Getter 提供全局访问点。
- Flutter应用: 全局配置、缓存、日志、数据库连接、某些全局状态(简单场景)、框架内部(如
Navigator)。 - 优点: 避免重复创建,节省资源;保证全局状态一致;访问方便。
- 注意: 单例用多了会增加代码耦合度。对于复杂的状态管理,优先考虑更专业的状态管理库(Provider, Riverpod, BLoC等),它们通常提供了比裸单例更强大和可控的机制。单例最适合那些**真正需要且确定是“独一份儿”**的服务或对象。
更多推荐
所有评论(0)