想象一下这个场景:

你开了一家超级火爆的奶茶店 🧋。店里有一台核心设备——唯一的收银机💰。这个收银机非常重要:

  1. 整个店只有一台: 你不能让每个店员自己带一台收银机,那样账就全乱了。
  2. 谁都需要用它: 点单员要记录订单,店长要查账,财务要结算,都得找这台机器。
  3. 随时能访问: 当顾客点单时,点单员必须能立刻找到并使用这台收银机。

在Flutter世界里,这个“唯一的收银机”就是“单例模式”!

单例模式是啥?一句话:

保证一个类在整个App运行期间,永远只有一个实例(对象),并且提供一个全局访问点,让任何地方的代码都能方便地拿到这个唯一的实例。

为啥在Flutter里需要它?

Flutter App 里有很多东西,你真心希望它只有一个,并且到处都能安全、方便地访问:

  1. 全局配置管理器: 存储 App 的主题颜色、语言设置、API 地址等。你肯定不希望不同的页面读到的主题颜色不一样吧?
  2. 缓存管理器: 管理内存中的图片缓存、数据缓存。多个缓存实例会导致内存浪费和数据不一致。
  3. 日志记录器: 所有地方的日志都应该汇集到同一个记录器里。
  4. 数据库连接: 通常,打开多个数据库连接是低效且容易出错的。
  5. 状态管理(部分场景): 当某些全局状态(比如用户登录信息)需要被非常多的组件访问,且严格保证一致性时,单例可以作为简单方案(虽然复杂状态更推荐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('购物车已清空!');
  }
}

关键点解析:

  1. static final CartManager _instance = CartManager._internal();:

    • static: 这个变量属于 CartManager 类本身,而不是类的某个具体对象。它在程序加载时就初始化了。
    • final: 确保 _instance 一旦被赋值(指向那个唯一的 CartManager 对象),就再也不能指向其他对象了。
    • CartManager._internal(): 调用私有的构造函数来创建这个唯一的实例。
  2. CartManager._internal(): 这是一个私有的命名构造函数。名字前面的下划线 _ 表示它是私有的。外部代码无法直接调用 new CartManager._internal() 来创建新实例,这就从语法上堵死了创建多个实例的可能。

  3. 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 对象。你添加商品、清空购物车,操作的都是这“独一份儿”的购物车。日志里也只会打印一次“购物车经理诞生了!”。

总结:单例模式就是你的“独一份儿”管家

  • 核心目标: 一个类,只有一个实例,全局都能访问。
  • 关键实现:
    1. 私有静态变量持有唯一实例。
    2. 私有构造函数(防止外部 new)。
    3. 公共静态方法/Getter 提供全局访问点。
  • Flutter应用: 全局配置、缓存、日志、数据库连接、某些全局状态(简单场景)、框架内部(如 Navigator)。
  • 优点: 避免重复创建,节省资源;保证全局状态一致;访问方便。
  • 注意: 单例用多了会增加代码耦合度。对于复杂的状态管理,优先考虑更专业的状态管理库(Provider, Riverpod, BLoC等),它们通常提供了比裸单例更强大和可控的机制。单例最适合那些**真正需要且确定是“独一份儿”**的服务或对象。
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐