Menu
Coddy logo textTech

Паттерн Singleton

Часть раздела Объектно-ориентированное программирование путешествия по Dart на Coddy — урок 90 из 110.

Паттерн Одиночка (Singleton) гарантирует, что у класса есть только один экземпляр во всем приложении, и предоставляет глобальную точку доступа к нему. Это полезно, когда для координации действий в системе требуется ровно один объект — например, менеджер конфигурации, соединение с базой данных или сервис логирования.

В Dart вы можете реализовать Singleton, используя фабричный конструктор в сочетании с приватным статическим экземпляром:

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() возвращает этот же экземпляр, поэтому изменения, внесенные через одну ссылку, видны повсюду.

Используйте синглтоны (Singletons) умеренно — они вводят глобальное состояние, что может затруднить тестирование. Они лучше всего подходят для действительно общих ресурсов, которым требуется согласованное состояние во всем приложении.

challenge icon

Задание

Легко

Давайте создадим менеджер настроек игры, используя паттерн Одиночка (Singleton)! Вы создадите систему, которая гарантирует существование только одного экземпляра настроек игры во всем приложении, чтобы все части вашей игры использовали одну и ту же конфигурацию.

Вы организуете свой код в двух файлах:

  • game_settings.dart: Создайте класс GameSettings, который реализует паттерн Singleton. Ваш менеджер настроек должен иметь:
    • Приватный статический финальный экземпляр (instance), который хранит единственный объект GameSettings
    • Приватный именованный конструктор для предотвращения создания экземпляров извне
    • Фабричный конструктор, который всегда возвращает один и тот же экземпляр
    • Три поля настроек: difficulty (String, по умолчанию: "Normal"), soundEnabled (bool, по умолчанию: true) и playerName (String, по умолчанию: "Player1")
    • Метод displaySettings(), который выводит текущие настройки в следующем формате: Settings: [playerName] | Difficulty: [difficulty] | Sound: [on/off] (выводите "on", если soundEnabled равно true, и "off", если false)
  • main.dart: Импортируйте настройки игры и продемонстрируйте, что Singleton работает правильно:
    • Получите экземпляр настроек и назовите его settings1
    • Вызовите displaySettings(), чтобы показать значения по умолчанию
    • Измените настройки: установите playerName в "Hero", difficulty в "Hard" и soundEnabled в false
    • Получите еще одну ссылку на настройки и назовите ее settings2
    • Вызовите displaySettings() для settings2, чтобы доказать, что он отражает изменения, сделанные через settings1
    • Выведите, являются ли settings1 и settings2 идентичными, используя формат: Same instance: [true/false]

Это демонстрирует главное преимущество Одиночек — изменения, внесенные в любом месте вашего приложения, немедленно становятся видимыми везде, потому что существует только один экземпляр!

Ожидаемый результат:

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

Шпаргалка

Паттерн Singleton (Одиночка) гарантирует, что у класса есть только один экземпляр во всем приложении, и предоставляет глобальную точку доступа к нему.

В Dart Singleton реализуется с помощью фабричного конструктора в сочетании с приватным статическим экземпляром:

class AppConfig {
  static final AppConfig _instance = AppConfig._internal();
  
  String appName = 'MyApp';
  String version = '1.0.0';
  
  // Приватный именованный конструктор
  AppConfig._internal();
  
  // Фабричный конструктор возвращает тот же самый экземпляр
  factory AppConfig() {
    return _instance;
  }
}

Ключевые компоненты:

  • static final _instance: Хранит единственный экземпляр, создаваемый один раз при загрузке класса
  • _internal(): Приватный конструктор предотвращает создание экземпляров извне
  • factory AppConfig(): Возвращает один и тот же экземпляр каждый раз

Пример использования:

var config1 = AppConfig();
var config2 = AppConfig();

config1.appName = 'UpdatedApp';

print(config2.appName);  // UpdatedApp
print(identical(config1, config2));  // true

Изменения, внесенные через одну ссылку, видны повсюду, так как все ссылки указывают на один и тот же экземпляр.

Попробуйте сами

import 'game_settings.dart';

void main() {
  // TODO: Получите экземпляр настроек и назовите его settings1
  
  // TODO: Вызовите displaySettings(), чтобы показать значения по умолчанию
  
  // TODO: Измените настройки:
  // - Измените playerName на "Hero"
  // - Измените difficulty на "Hard"
  // - Измените soundEnabled на false
  
  // TODO: Получите еще одну ссылку на настройки и назовите ее settings2
  
  // TODO: Вызовите displaySettings() для settings2, чтобы доказать, что изменения отражены
  
  // TODO: Выведите, являются ли settings1 и settings2 идентичными
  // Формат: Same instance: [true/false]
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование