设计模式在Flutter中的应用:单例、工厂、观察者
引言:写代码,也需要"设计图纸"
开发Flutter应用时,不知道你有没有遇到过这种情况:项目越来越庞大,功能模块四处散落,改一处代码怕影响其他地方,新同事看代码半天理不清头绪......随着业务复杂度的增加,如何组织代码成了我们必须面对的挑战。
这时候,设计模式就能派上大用场了。它们不是什么高深的魔法,更像是软件世界里经过千锤百炼的"设计图纸"或"最佳实践菜谱",专门用来解决那些在特定场景下反复出现的编程问题。用好它们,能让你的代码更健壮、更清晰,也更好维护。
在这篇文章里,咱们就一起看看Flutter开发中三个非常实用、出镜率很高的设计模式:单例(Singleton)、工厂(Factory)和观察者(Observer)。我们不会只空谈概念,而是会结合Dart和Flutter的特点,通过实际可运行的例子,聊聊它们具体怎么用,用的时候又该注意哪些"坑"。你会发现,这些模式能实实在在地帮你:
- 写出更易懂、更好维护的代码:结构清晰,自己和队友都省心。
- 告别重复劳动:遵循"不要重复你自己"(DRY)的原则。
- 让团队协作更顺畅:大家用共同的设计语言和实现方式。
- 打下坚实的应用架构基础:尤其是在状态管理、对象创建和组件通信这些关键地方。
模式解析:在Flutter的上下文里理解它们
1. 单例模式:独一无二的"大管家"
单例模式的核心很简单:确保一个类只有一个实例,并且提供一个全局的访问点。在Flutter里,它特别适合用来管理那些你希望全局唯一、又经常需要用到的东西。
Dart是怎么优雅实现单例的? Dart语言本身对单例就很友好,通常用 factory 构造函数加一个 static 的实例变量就能搞定。factory 构造函数的神奇之处在于,它可以决定是返回一个全新的对象,还是把一个现有的实例给你。再配合 late 关键字,我们就能轻松实现"懒加载"------只有在第一次真正用到它的时候才创建,避免应用一启动就初始化所有东西。
Flutter里哪些地方会用到它?
- 各种全局服务 :比如网络请求库(像
Dio)的实例、数据库访问对象、日志记录器。 - 简单的应用状态:在一些不太复杂的场景里,用来管理用户登录信息、应用主题配置等。
- 资源池:比如数据库连接池。
来看看代码怎么写:
dart
class AppConfig {
// 静态的、唯一的实例,用late实现懒加载
static late final AppConfig _instance;
// 私有构造函数,关上门,不让别人随便new
AppConfig._internal() {
// 在这里进行初始化
_apiEndpoint = 'https://api.example.com';
_appName = 'MyFlutterApp';
}
// 对外的工厂构造函数,永远返回那个唯一的_instance
factory AppConfig() {
return _instance;
}
// 提供一个静态初始化方法,让我们能控制初始化的时机
static Future<void> init() async {
_instance = AppConfig._internal();
await _instance._loadFromCache(); // 假设有个异步的加载过程
}
String _apiEndpoint;
String _appName;
// ... 其他属性和方法
}
使用的时候,先在 main() 函数里调用 await AppConfig.init(); 初始化,之后在任何地方 AppConfig() 拿到的都是同一个配置对象。
2. 工厂模式:Widget的"智能创建车间"
工厂模式定义了一个创建对象的接口,但把具体创建哪个类的决定权留给了子类。在Flutter这个UI驱动的框架里,它简直是为"根据不同条件创建不同Widget"这种场景量身定做的。
它在Flutter里的优势很明显:
- 封装创建逻辑 :把一堆
if-else或者复杂的构造逻辑打包起来,让你的build方法保持清爽。 - 灵活返回:可以返回一个抽象类型,具体是什么Widget,运行时再决定,扩展性很强。
- 与Widget树天然契合:直接在UI层调用,根据应用状态动态决定显示什么。
典型使用场景:
- 创建适配iOS/Android等不同平台风格的组件(比如对话框)。
- 根据后台返回的数据类型,渲染对应的展示组件。
- 实现依赖注入,为测试环境和生产环境提供不同的服务实现。
3. 观察者模式:Flutter响应式的"心脏"
观察者模式描述了一种一对多的依赖关系:一个对象(主题)状态一变,所有依赖它的对象(观察者)都会自动收到通知并更新。
其实,Flutter骨子里就是观察者模式。 想想看,StatefulWidget 的 setState() 不就是最直接的体现吗?状态(State)一变,相关的Widget就重新构建。更进一步,Flutter SDK自带的 ChangeNotifier 和 ValueNotifier 类,提供了更轻量、更通用的观察者模式实现,这也是 provider 这类状态管理库的基石。
在Flutter里,它们的角色是:
- 主题(Subject) :通常是继承自
ChangeNotifier的一个数据模型类。 - 观察者(Observer) :可以是
Listener、ValueListenableBuilder这种Widget,或者是通过provider的Consumer、Watch来监听的Widget。 - 发通知 :在主题类里调用
notifyListeners(),所有观察者就被唤醒了。
实战:把模式写成代码
示例1:单例模式 - 一个全局日志记录器
dart
// logger.dart
import 'dart:io';
import 'package:path_provider/path_provider.dart';
class AppLogger {
// 经典的单例写法:静态final实例 + 私有构造函数
static final AppLogger _instance = AppLogger._internal();
late File _logFile;
factory AppLogger() {
return _instance;
}
AppLogger._internal(); // 私有构造函数
// 初始化日志文件路径
Future<void> init() async {
final directory = await getApplicationDocumentsDirectory();
_logFile = File('${directory.path}/app.log');
await _logFile.create();
}
// 写日志
Future<void> log(String message) async {
final timestamp = DateTime.now().toIso8601String();
final entry = '$timestamp: $message\n';
// 加个try-catch,写日志失败也别让应用崩溃
try {
await _logFile.writeAsString(entry, mode: FileMode.append);
} catch (e) {
print('日志写入失败: $e');
}
}
Future<String> getLogs() async {
try {
return await _logFile.readAsString();
} catch (e) {
return '暂无日志。';
}
}
}
// 在 main.dart 中使用
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await AppLogger().init(); // 启动时初始化单例
runApp(const MyApp());
}
// 在某个页面中使用
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('单例模式演示')),
body: Center(
child: ElevatedButton(
onPressed: () async {
await AppLogger().log('在首页按下了按钮');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('事件已记录!')),
);
},
child: const Text('记录一个事件'),
),
),
);
}
}
示例2:工厂模式 - 一个懂"看人下菜碟"的对话框
dart
// dialog_factory.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
// 抽象接口,定义对话框都要能构建
abstract class PlatformAlertDialog {
Widget build(BuildContext context);
}
// Material Design 风格的实现
class MaterialAlertDialog implements PlatformAlertDialog {
final String title;
final String content;
MaterialAlertDialog({required this.title, required this.content});
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(title),
content: Text(content),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('确定'),
),
],
);
}
}
// Cupertino (iOS) 风格的实现
class CupertinoAlertDialog implements PlatformAlertDialog {
final String title;
final String content;
CupertinoAlertDialog({required this.title, required this.content});
@override
Widget build(BuildContext context) {
return CupertinoAlertDialog(
title: Text(title),
content: Text(content),
actions: [
CupertinoDialogAction(
onPressed: () => Navigator.of(context).pop(),
child: const Text('确定'),
),
],
);
}
}
// 核心工厂类
class DialogFactory {
static PlatformAlertDialog createAlertDialog({
required BuildContext context,
required String title,
required String content,
}) {
// 根据当前运行平台,决定生产哪个具体的对话框
switch (Theme.of(context).platform) {
case TargetPlatform.iOS:
case TargetPlatform.macOS:
return CupertinoAlertDialog(title: title, content: content);
// Android、Windows等其他平台
default:
return MaterialAlertDialog(title: title, content: content);
}
}
}
// 在Widget中使用
class FactoryPatternDemo extends StatelessWidget {
const FactoryPatternDemo({super.key});
void _showPlatformDialog(BuildContext context) {
// 使用工厂创建对话框,无需关心具体类型
final dialog = DialogFactory.createAlertDialog(
context: context,
title: '工厂模式',
content: '这个对话框会自动适应你操作系统的设计风格!',
);
showDialog(
context: context,
builder: (context) => dialog.build(context), // 调用统一的build方法
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('工厂模式演示')),
body: Center(
child: ElevatedButton(
onPressed: () => _showPlatformDialog(context),
child: const Text('显示自适应对话框'),
),
),
);
}
}
示例3:观察者模式 - 一个会"喊人"的购物车
dart
// shopping_cart.dart
import 'package:flutter/foundation.dart'; // 引入ChangeNotifier
class CartItem {
final String id;
final String name;
final double price;
int quantity;
CartItem({
required this.id,
required this.name,
required this.price,
this.quantity = 1,
});
double get totalPrice => price * quantity;
}
// 购物车本身,它继承自ChangeNotifier,是个"主题"
class ShoppingCart extends ChangeNotifier {
final List<CartItem> _items = [];
// 对外提供只读的商品列表
List<CartItem> get items => List.unmodifiable(_items);
// 计算总商品数
int get itemCount => _items.fold(0, (sum, item) => sum + item.quantity);
// 计算总金额
double get totalAmount =>
_items.fold(0, (sum, item) => sum + item.totalPrice);
// 添加商品
void addItem(CartItem newItem) {
final index = _items.indexWhere((item) => item.id == newItem.id);
if (index >= 0) {
// 已有商品,增加数量
_items[index].quantity += newItem.quantity;
} else {
// 新商品,加入列表
_items.add(newItem);
}
// 关键一步:数据变了,通知所有监听者(比如UI)更新
notifyListeners();
}
void removeItem(String itemId) {
_items.removeWhere((item) => item.id == itemId);
notifyListeners();
}
void clear() {
_items.clear();
notifyListeners();
}
}
// 在UI层使用Provider(它是观察者模式的一种优秀实践)来监听
// main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'shopping_cart.dart';
void main() {
runApp(
// 在应用顶层提供我们的购物车(主题)
ChangeNotifierProvider(
create: (_) => ShoppingCart(),
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: CartScreen(),
);
}
}
class CartScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Consumer是一个Widget观察者,当ShoppingCart通知时,它的builder会重建
return Scaffold(
appBar: AppBar(title: const Text('观察者模式 - 购物车')),
body: Consumer<ShoppingCart>(
builder: (context, cart, child) {
return Column(
children: [
Expanded(
child: ListView.builder(
itemCount: cart.items.length,
itemBuilder: (ctx, index) {
final item = cart.items[index];
return ListTile(
title: Text(item.name),
subtitle: Text('数量: ${item.quantity}'),
trailing: Text('\$${item.totalPrice.toStringAsFixed(2)}'),
onTap: () => cart.removeItem(item.id), // 点击删除
);
},
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'总计: \$${cart.totalAmount.toStringAsFixed(2)}',
style: Theme.of(context).textTheme.headline6,
),
ElevatedButton(
onPressed: () {
// 添加商品,这会自动触发UI更新
cart.addItem(CartItem(
id: DateTime.now().millisecondsSinceEpoch.toString(),
name: '示例商品',
price: 9.99,
));
},
child: const Text('添加示例商品'),
),
],
),
),
],
);
},
),
);
}
}
避坑指南:用得好,更要用得巧
-
单例模式,要小心这些:
- 懒加载还是饿汉式? Dart里
static final instance = Class._()是饿汉式,类一加载就创建。如果实例初始化很重,或者想精准控制时机,就用late final实现懒加载。 - 小心内存泄漏 :单例活的和应用一样长。如果它引用了
BuildContext或其他大对象,记得在合适时机(比如页面销毁)清理引用,或者设计dispose方法。 - 别让测试变困难:单例的全局状态会让单元测试互相干扰。尽量让单例无状态,或者提供一个静态方法在测试时重置它。
- 懒加载还是饿汉式? Dart里
-
工厂模式,可以更高效:
- 考虑缓存:如果创建对象成本很高(比如解析复杂配置),且对象可复用,可以在工厂里加个缓存池。
- 多用
const:如果工厂创建出的Widget或对象是静态不变的,一定要用const构造函数。Flutter会对相同的const实例进行复用,性能提升显著。 - 别在
build里干重活 :复杂的工厂创建逻辑尽量不要直接放在build方法里。可以提到initState中,或者用缓存技术(Memoization)来避免重复计算。
-
观察者模式,这些点至关重要:
- 记得取消订阅! 这是Flutter里最常见的性能陷阱之一。手动
addListener就必须在dispose里配对一个removeListener。更推荐使用ValueListenableBuilder、AnimatedBuilder或provider的Consumer,它们会自动管理生命周期。 - 重建范围要最小化 :一个
notifyListeners()可能会引起大片UI重建。利用provider的Selector或ValueListenableBuilder的child参数,可以把重建精确控制在真正依赖变化数据的那一小块Widget上。 - 别在通知时改状态 :在
notifyListeners()方法里不要再修改状态,否则容易导致无限循环或状态混乱。应该先完成所有状态变更,最后一次性通知。
- 记得取消订阅! 这是Flutter里最常见的性能陷阱之一。手动
写在最后:模式是工具,不是枷锁
通过上面的探讨,我们可以看到:
- 单例模式 是管理全局资源的利器,Dart用
factory和static就能优雅实现,但要注意它对可测试性的影响。 - 工厂模式 与Flutter动态构建UI的需求天作之合,它能让你干净利落地封装对象创建逻辑。
- 观察者模式 是Flutter响应式编程的核心,从
setState到provider,都在用它同步数据和UI。
最后想说的是,设计模式是为你服务的工具,而不是必须遵守的教条。千万别为了用模式而用模式。在Flutter开发中,理解这些模式背后的思想,再结合框架本身的特性(比如Widget不可变、响应式更新),灵活地运用,才能真正提升你的代码质量。
如果你已经掌握了这三种基础模式,可以继续探索在Flutter中同样常见的 仓库模式(Repository) (用于抽象数据源)、建造者模式(Builder) (很多Widget的 copyWith 方法就是它的体现),以及 适配器模式(Adapter)(用于整合不同接口的库)。不断学习和实践,你的工程能力一定会越来越强。