Flutter艺术探索-设计模式在Flutter中的应用:单例、工厂、观察者

设计模式在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里的优势很明显:

  1. 封装创建逻辑 :把一堆 if-else 或者复杂的构造逻辑打包起来,让你的 build 方法保持清爽。
  2. 灵活返回:可以返回一个抽象类型,具体是什么Widget,运行时再决定,扩展性很强。
  3. 与Widget树天然契合:直接在UI层调用,根据应用状态动态决定显示什么。

典型使用场景:

  • 创建适配iOS/Android等不同平台风格的组件(比如对话框)。
  • 根据后台返回的数据类型,渲染对应的展示组件。
  • 实现依赖注入,为测试环境和生产环境提供不同的服务实现。

3. 观察者模式:Flutter响应式的"心脏"

观察者模式描述了一种一对多的依赖关系:一个对象(主题)状态一变,所有依赖它的对象(观察者)都会自动收到通知并更新。

其实,Flutter骨子里就是观察者模式。 想想看,StatefulWidgetsetState() 不就是最直接的体现吗?状态(State)一变,相关的Widget就重新构建。更进一步,Flutter SDK自带的 ChangeNotifierValueNotifier 类,提供了更轻量、更通用的观察者模式实现,这也是 provider 这类状态管理库的基石。

在Flutter里,它们的角色是:

  • 主题(Subject) :通常是继承自 ChangeNotifier 的一个数据模型类。
  • 观察者(Observer) :可以是 ListenerValueListenableBuilder 这种Widget,或者是通过 providerConsumerWatch 来监听的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('添加示例商品'),
                    ),
                  ],
                ),
              ),
            ],
          );
        },
      ),
    );
  }
}

避坑指南:用得好,更要用得巧

  1. 单例模式,要小心这些:

    • 懒加载还是饿汉式? Dart里 static final instance = Class._() 是饿汉式,类一加载就创建。如果实例初始化很重,或者想精准控制时机,就用 late final 实现懒加载。
    • 小心内存泄漏 :单例活的和应用一样长。如果它引用了BuildContext或其他大对象,记得在合适时机(比如页面销毁)清理引用,或者设计 dispose 方法。
    • 别让测试变困难:单例的全局状态会让单元测试互相干扰。尽量让单例无状态,或者提供一个静态方法在测试时重置它。
  2. 工厂模式,可以更高效:

    • 考虑缓存:如果创建对象成本很高(比如解析复杂配置),且对象可复用,可以在工厂里加个缓存池。
    • 多用const :如果工厂创建出的Widget或对象是静态不变的,一定要用 const 构造函数。Flutter会对相同的const实例进行复用,性能提升显著。
    • 别在build里干重活 :复杂的工厂创建逻辑尽量不要直接放在 build 方法里。可以提到 initState 中,或者用缓存技术(Memoization)来避免重复计算。
  3. 观察者模式,这些点至关重要:

    • 记得取消订阅! 这是Flutter里最常见的性能陷阱之一。手动 addListener 就必须在 dispose 里配对一个 removeListener。更推荐使用 ValueListenableBuilderAnimatedBuilderproviderConsumer,它们会自动管理生命周期。
    • 重建范围要最小化 :一个 notifyListeners() 可能会引起大片UI重建。利用 providerSelectorValueListenableBuilderchild 参数,可以把重建精确控制在真正依赖变化数据的那一小块Widget上。
    • 别在通知时改状态 :在 notifyListeners() 方法里不要再修改状态,否则容易导致无限循环或状态混乱。应该先完成所有状态变更,最后一次性通知。

写在最后:模式是工具,不是枷锁

通过上面的探讨,我们可以看到:

  • 单例模式 是管理全局资源的利器,Dart用 factorystatic 就能优雅实现,但要注意它对可测试性的影响。
  • 工厂模式 与Flutter动态构建UI的需求天作之合,它能让你干净利落地封装对象创建逻辑。
  • 观察者模式 是Flutter响应式编程的核心,从 setStateprovider,都在用它同步数据和UI。

最后想说的是,设计模式是为你服务的工具,而不是必须遵守的教条。千万别为了用模式而用模式。在Flutter开发中,理解这些模式背后的思想,再结合框架本身的特性(比如Widget不可变、响应式更新),灵活地运用,才能真正提升你的代码质量。

如果你已经掌握了这三种基础模式,可以继续探索在Flutter中同样常见的 仓库模式(Repository) (用于抽象数据源)、建造者模式(Builder) (很多Widget的 copyWith 方法就是它的体现),以及 适配器模式(Adapter)(用于整合不同接口的库)。不断学习和实践,你的工程能力一定会越来越强。

相关推荐
子春一2 小时前
Flutter for OpenHarmony:构建一个工业级 Flutter 计算器,深入解析表达式解析、状态管理与 Material 3 交互设计
flutter·交互
晚霞的不甘2 小时前
Flutter for OpenHarmony字典查询 App 全栈解析:从搜索交互到详情展示的完整实
flutter·架构·前端框架·全文检索·交互·个人开发
2601_949847752 小时前
Flutter for OpenHarmony 剧本杀组队App实战:关于我们页面实现
开发语言·javascript·flutter
子春一2 小时前
Flutter for OpenHarmony:构建一个交互式 Flutter RGB 颜色选择器,深入解析状态驱动 UI、HEX 转换与无障碍色彩对比
flutter·ui
IT陈图图2 小时前
Flutter × OpenHarmony 实战:优雅构建确认对话框的组件化方案
开发语言·javascript·flutter
雨季6662 小时前
Flutter 三端应用实战:OpenHarmony 简易文本末尾字符查看器开发指南
开发语言·javascript·flutter
雨季6662 小时前
Flutter 三端应用实战:OpenHarmony 简易文本首字母提取器开发指南
flutter·ui·dart
IT陈图图5 小时前
构建 Flutter × OpenHarmony 跨端带文本输入对话框示例
开发语言·javascript·flutter
2601_9495430111 小时前
Flutter for OpenHarmony垃圾分类指南App实战:我的成就实现
flutter