Skip to content

Паттерн Singleton

Добро пожаловать в руководство по использованию паттерна Singleton в языке программирования Dart. Этот паттерн проектирования является одним из самых распространённых и простых, но при этом невероятно полезным инструментом в арсенале разработчика.


Что такое Singleton?

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

Что такое Singleton


Зачем нужен Singleton?

Паттерн Singleton решает несколько распространённых задач:

  • Экономия ресурсов: создание тяжёлых объектов, например, подключения к БД.
  • Глобальная точка доступа: единый доступ к данным (например, к настройкам приложения).
  • Координация состояния: синхронизация состояния между разными частями программы.

Реализация Singleton в Dart

В Dart существует несколько способов реализации паттерна Singleton. Разберем два наиболее популярных:

1. Через фабричный конструктор

class AppSettings {
  // Приватный статический экземпляр (eager initialization)
  static final AppSettings _instance = AppSettings._internal();
 
  // Фабричный конструктор
  factory AppSettings() {
    return _instance;
  }
 
  // Приватный конструктор
  AppSettings._internal();
 
  // Настройки приложения
  bool isDarkMode = false;
  String language = 'ru';
  double fontSize = 14.0;
 
  void saveSettings() {
    print('Настройки сохранены: тема = $isDarkMode, язык = $language, шрифт = $fontSize');
  }
}

Особенности:

  • Экземпляр создаётся сразу при загрузке класса (жадная инициализация).
  • Полная потокобезопасность благодаря однопоточному исполнению в Dart.

2. Через ленивую инициализацию

class DataCache {
  // Приватный статический экземпляр (lazy initialization)
  static DataCache? _instance;
 
  // Геттер для получения экземпляра
  static DataCache get instance {
    _instance ??= DataCache._internal();
    return _instance!;
  }
 
  // Приватный конструктор
  DataCache._internal();
 
  // Кеш данных
  final Map<String, dynamic> _cache = {};
 
  Future<dynamic> getData(String key) async {
    if (_cache.containsKey(key)) {
      print('Получение из кеша: $key');
      return _cache[key];
    }
 
    print('Загрузка данных: $key');
    await Future.delayed(Duration(seconds: 2)); // эмуляция задержки
 
    final data = 'Данные для $key (${DateTime.now()})';
    _cache[key] = data;
    return data;
  }
 
  void clearCache() {
    _cache.clear();
    print('Кеш очищен');
  }
}

Особенности:

  • Экземпляр создаётся только при первом обращении.
  • Удобно, когда создание экземпляра "тяжёлое" или может не понадобиться сразу.

Быстрая таблица: eager vs lazy

Тип Когда создаётся Плюсы Минусы
Eager При старте программы Простота, надёжность Занимает память сразу
Lazy При первом использовании Экономия ресурсов Нужен осторожный код при многопоточности

Когда использовать Singleton

Когда использовать Singleton?

Применение Singleton оправдано в следующих случаях:

  • Работа с подключением к базе данных.
  • Менеджеры состояния и кэширования.
  • Глобальные настройки приложения.
  • Классы-обёртки для внешних сервисов.

Недостатки Singleton

Несмотря на удобство, Singleton имеет недостатки:

  1. Глобальное состояние Усложняет понимание работы приложения и его тестирование.

  2. Жёсткая связанность компонентов Модули напрямую зависят от конкретной реализации Singleton.

  3. Трудности с модульным тестированием Подменить Singleton на мок-объект непросто без дополнительных абстракций.

  4. Потенциальные утечки памяти Singleton "живет" всё время работы приложения — объекты внутри него никогда не удаляются.

  5. Сложность масштабирования В многопоточной среде (например, при работе с isolates) Singleton нужно реализовывать осторожно.


Заключение

Паттерн Singleton — мощный инструмент в разработке приложений. Dart делает его реализацию особенно элегантной благодаря фабричным конструкторам и возможностям ленивой инициализации.

Главное правило: Использовать Singleton там, где это оправдано, а не по умолчанию!


Полезные ссылки


Домашнее задание

Базовый уровень: Создайте класс Logger с использованием Singleton. Методы:

  • log(String message): пишет сообщение в консоль и сохраняет его в список истории.
  • clear(): очищает историю.
  • history: возвращает все сохранённые сообщения.

(Подсказка: используйте List для хранения истории.)


Средний уровень: Реализуйте класс UserSession для хранения информации о текущем пользователе. Методы:

  • login(String username)
  • logout()
  • isLoggedIn()

(Подсказка: сохраняйте имя пользователя в поле класса.)


Продвинутый уровень: Создайте менеджер загрузки изображений ImageLoader. Методы:

  • load(String url): загружает изображение (если уже есть в кеше — возвращает кешированное).
  • clearCache()
  • cacheStats(): количество кешированных изображений.

(Подсказка: используйте Map<String, dynamic> для кеша.)


Экспертный уровень: Реализуйте DatabaseConnectionManager с поддержкой двух типов подключений:

  • основная база (primary)
  • база только для чтения (readReplica)

Используйте абстрактную фабрику для создания нужного подключения.

(Подсказка: определите enum ConnectionType.)

28 апр. 2025 г.
andron13