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)(用于整合不同接口的库)。不断学习和实践,你的工程能力一定会越来越强。

相关推荐
恋猫de小郭42 分钟前
Android 禁止侧载将正式实施,需要等待 24 小时冷静期
android·flutter·harmonyos
FFF-X1 小时前
解决 Flutter Gradle 下载报错:修改默认 distributionUrl
flutter
程序员Ctrl喵20 小时前
异步编程:Event Loop 与 Isolate 的深层博弈
开发语言·flutter
前端不太难1 天前
Flutter 如何设计可长期维护的模块边界?
flutter
小蜜蜂嗡嗡1 天前
flutter列表中实现置顶动画
flutter
始持1 天前
第十二讲 风格与主题统一
前端·flutter
始持1 天前
第十一讲 界面导航与路由管理
flutter·vibecoding
始持1 天前
第十三讲 异步操作与异步构建
前端·flutter
新镜1 天前
【Flutter】 视频视频源横向、竖向问题
flutter
黄林晴1 天前
Compose Multiplatform 1.10 发布:统一 Preview、Navigation 3、Hot Reload 三箭齐发
android·flutter