Menu
CoddyTech

单例模式

本课属于 Coddy Dart 学习旅程的“面向对象编程”单元。第 90 课,共 110 课。

单例模式可确保一个类在整个应用程序中只有一个实例,并提供全局访问该实例的入口。当系统需要一个对象来协调各项操作时,这种模式就很有用,例如配置管理器、数据库连接或日志服务。

在 Dart 中,你可以将工厂构造函数与私有静态实例结合起来实现单例:

class AppConfig {
  static final AppConfig _instance = AppConfig._internal();
  
  String appName = 'MyApp';
  String version = '1.0.0';
  
  // 私有命名构造函数
  AppConfig._internal();
  
  // 工厂构造函数返回同一个实例
  factory AppConfig() {
    return _instance;
  }
}

void main() {
  var config1 = AppConfig();
  var config2 = AppConfig();
  
  config1.appName = 'UpdatedApp';
  
  print(config2.appName);  // UpdatedApp
  print(identical(config1, config2));  // true
}

私有构造函数 _internal() 可以防止外部实例化。静态字段 _instance 保存唯一的实例,该实例会在类加载时创建一次。每次调用 AppConfig() 都会返回同一个实例,因此通过一个引用所做的更改在任何地方都可见。

谨慎使用单例,因为它们会引入全局状态,可能让测试变得更困难。它们最适合用于需要在整个应用中保持状态一致的共享资源。

challenge icon

挑战

简单

让我们使用单例模式来构建一个游戏设置管理器!你将创建一个系统,确保整个应用中只有一个游戏设置实例,这样游戏的所有部分都能共享相同的配置。

你将把代码整理到两个文件中:

  • game_settings.dart:创建一个实现单例模式的 GameSettings 类。你的设置管理器应包含:
    • 一个私有的静态 final 实例,用于保存唯一的 GameSettings 对象
    • 一个私有命名构造函数,用于防止外部创建实例
    • 一个始终返回同一实例的工厂构造函数
    • 三个设置字段:difficulty(String,默认值:"Normal")、soundEnabled(bool,默认值:true)和 playerName(String,默认值:"Player1")
    • 一个方法 displaySettings(),用于按以下格式打印当前设置:Settings: [playerName] | Difficulty: [difficulty] | Sound: [on/off](如果 soundEnabled 为 true,则打印 "on";如果为 false,则打印 "off")
  • main.dart:导入游戏设置,并演示单例模式能正常工作:
    • 获取设置实例,并将其命名为 settings1
    • 调用 displaySettings() 来显示默认值
    • 修改设置:将 playerName 改为 "Hero",将 difficulty 改为 "Hard",并将 soundEnabled 改为 false
    • 再获取一个设置引用,并将其命名为 settings2
    • 对 settings2 调用 displaySettings(),以证明它反映了通过 settings1 所做的更改
    • 使用以下格式打印 settings1 和 settings2 是否相同:Same instance: [true/false]

这展示了单例的核心优势:由于只有一个实例,因此在应用中的任何位置所做的更改都会立即在其他所有位置生效!

预期输出:

Settings: Player1 | Difficulty: Normal | Sound: on
Settings: Hero | Difficulty: Hard | Sound: off
Same instance: true

亲手试试

import 'game_settings.dart';

void main() {
  // TODO:获取设置实例,并将其命名为 settings1
  
  // TODO:调用 displaySettings() 显示默认值
  
  // TODO:修改设置:
  // 将 playerName 改为 "Hero"
  // 将 difficulty 改为 "Hard"
  // 将 soundEnabled 改为 false
  
  // TODO:获取设置的另一个引用,并将其命名为 settings2
  
  // TODO:通过 settings2 调用 displaySettings(),证明它反映了这些更改
  
  // TODO:输出 settings1 和 settings2 是否是同一个实例
  // 格式:Same instance: [true/false]
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Dart 在线编译器