Паттерн 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) умеренно — они вводят глобальное состояние, что может затруднить тестирование. Они лучше всего подходят для действительно общих ресурсов, которым требуется согласованное состояние во всем приложении.
Задание
ЛегкоДавайте создадим менеджер настроек игры, используя паттерн Одиночка (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)
- Приватный статический финальный экземпляр (instance), который хранит единственный объект
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]
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыБиблиотеки и импортВведение в ООПКлассы и объектыКлючевое слово thisМетодыПеременные экземпляраОсновы конструкторовИтоги — Простой калькулятор4Null-безопасность
Введение в Null-безопасностьNullable и Non-Nullable типыОператоры ? и !Ключевое слово Late и Null-безопасностьNull-aware операторыNull-безопасность в классахИтоги — Система профилей пользователей7Абстрактные классы и интерфейсы
Абстрактные классыАбстрактные методыИнтерфейсы в DartНеявные интерфейсыРеализация против наследованияМножественные интерфейсыИтоги — Калькулятор фигур10Коллекции и обобщения
Обзор List, Set, MapТипобезопасные коллекцииОбобщенные классыОбобщенные методыОграничения обобщенийIterable и IteratorИтоги: Обобщенное хранилище13Продвинутые концепции ООП
Композиция против наследованияМетоды расширенияВызываемые классыЗапечатанные классы (Dart 3)Записи (Dart 3)Паттерны и сопоставление (3.0)Перечисления с методами16Проект: Управление библиотекой
Обзор проектаКлассы Book и User2Конструкторы в Dart
Конструктор по умолчаниюИменованные конструкторыСписки инициализацииКонстантные конструкторыФабричные конструкторыПеренаправляющие конструкторыИтоги — Shape Builder5Инкапсуляция
Публичные и приватные членыСоглашение о префиксе _Приватность на уровне библиотекГеттеры и сеттеры: подробный разборСкрытие данныхИтоги — Записи о студентах8Mixins
Введение в MixinsСоздание MixinsИспользование нескольких MixinsКлючевое слово on в MixinsMixin и наследованиеMixin и интерфейсИтоги: Система животных11Специальные методы
Переопределение toString()Переопределение hashCode и ==Интерфейс ComparableМетод call()Переопределение noSuchMethodПовторение — Пользовательская коллекция14Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy3Свойства классов
Члены экземпляра и статические членыПоля Final и ConstПеременные LateСтатические методы и поляГеттеры и сеттерыИтоги — Менеджер банковского счета6Наследование
Основы наследованияКлючевое слово superПереопределение методовАннотация @overrideКлючевое слово final для классовКонструкторы и наследованиеИтоги: Иерархия сотрудников9Полиморфизм
Основы полиморфизмаПолиморфизм через интерфейсыПроверка типов во время выполненияОператоры is и asКлючевое слово covariantИтоги — Обработчик платежей12Асинхронное ООП
Futures и async/awaitОсновы StreamsКонтроллеры StreamАсинхронные конструкторыАсинхронность в методах классовИтоги — Data Fetcher15Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий