Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Пример использования Singleton в Dart
Singleton — это паттерн проектирования, который гарантирует, что класс имеет только один экземпляр (инстанс) в течение всей жизни приложения. Рассмотрю несколько способов реализации и когда его использовать.
Вариант 1: Классический Singleton
// ✅ Традиционная реализация
class Database {
// Статический приватный экземпляр
static Database? _instance;
// Приватный конструктор
Database._internal();
// Factory конструктор для доступа
factory Database() {
// Если экземпляра нет — создаём
_instance ??= Database._internal();
return _instance!;
}
void connect() {
print('Database connected');
}
Future<List<User>> getUsers() async {
// Выполняем запрос
return [];
}
}
// Использование
void main() {
final db1 = Database(); // Создание первого экземпляра
db1.connect(); // Подключение
final db2 = Database(); // Получение того же экземпляра
print(identical(db1, db2)); // true — один и тот же объект!
// Гарантировано только одно подключение к БД
}
Вариант 2: Ленивый Singleton (рекомендуется)
// ✅ Ленивая инициализация (лучше)
class Config {
static late final Config _instance = Config._internal();
// Приватный конструктор
Config._internal();
// Factory для доступа
factory Config() => _instance;
String apiUrl = 'https://api.example.com';
int timeout = 30;
bool enableLogging = true;
void loadFromFile() {
print('Loading configuration...');
// Загрузка настроек из файла
}
}
// Использование
void main() async {
// Инстанс создаётся при первом обращении
final config = Config();
config.loadFromFile();
print(config.apiUrl);
// Второй доступ вернёт тот же инстанс
final config2 = Config();
print(identical(config, config2)); // true
}
Вариант 3: С инициализацией (лучший для production)
// ✅ Singleton с контролем инициализации
class ApiClient {
static late final ApiClient _instance;
final String baseUrl;
late final HttpClient _httpClient;
ApiClient._internal({required this.baseUrl});
// Инициализация (вызывается явно в main)
static Future<void> initialize({required String baseUrl}) async {
_instance = ApiClient._internal(baseUrl: baseUrl);
await _instance._setup();
}
// Доступ к инстансу
static ApiClient get instance {
if (_instance == null) {
throw Exception('ApiClient not initialized. Call initialize() first.');
}
return _instance;
}
Future<void> _setup() async {
print('Setting up API client...');
_httpClient = HttpClient();
_httpClient.userAgent = 'Flutter App/1.0';
}
Future<Map<String, dynamic>> get(String endpoint) async {
final request = await _httpClient.getUrl(Uri.parse('$baseUrl$endpoint'));
final response = await request.close();
return {};
}
}
// Использование в main
void main() async {
// Инициализируем до использования
await ApiClient.initialize(baseUrl: 'https://api.example.com');
// Теперь можно использовать
final client = ApiClient.instance;
final data = await client.get('/users');
runApp(MyApp());
}
// В любом месте приложения
class UserRepository {
final _client = ApiClient.instance; // Получаем singleton
Future<List<User>> getUsers() async {
final data = await _client.get('/users');
return [];
}
}
Вариант 4: С GetIt (рекомендуется для больших проектов)
// ✅ С использованием пакета GetIt (лучше для архитектуры)
import 'package:get_it/get_it.dart';
final getIt = GetIt.instance;
// Классы сервисов
class Logger {
void log(String message) => print('[LOG] $message');
}
class Database {
final Logger _logger;
Database(this._logger);
void connect() {
_logger.log('Connecting to database...');
}
}
class ApiClient {
final Logger _logger;
final Database _database;
ApiClient(this._logger, this._database);
Future<Map> get(String endpoint) async {
_logger.log('GET $endpoint');
return {};
}
}
// Инициализация в main
void main() {
// Регистрируем singleton'ы
getIt.registerSingleton<Logger>(Logger());
getIt.registerSingleton<Database>(Database(getIt<Logger>()));
getIt.registerSingleton<ApiClient>(
ApiClient(getIt<Logger>(), getIt<Database>()),
);
runApp(MyApp());
}
// Использование
class UserRepository {
final _logger = getIt<Logger>();
final _api = getIt<ApiClient>();
Future<List<User>> getUsers() async {
_logger.log('Fetching users...');
return [];
}
}
Практический пример: SharedPreferences Singleton
// ✅ Реальный пример
class PreferencesManager {
static late final PreferencesManager _instance;
late SharedPreferences _prefs;
PreferencesManager._internal();
factory PreferencesManager() => _instance;
// Инициализация (вызывается в main)
static Future<void> initialize() async {
_instance = PreferencesManager._internal();
_instance._prefs = await SharedPreferences.getInstance();
}
// Методы для доступа к preference'ам
String? getString(String key) => _prefs.getString(key);
Future<bool> setString(String key, String value) =>
_prefs.setString(key, value);
int? getInt(String key) => _prefs.getInt(key);
Future<bool> setInt(String key, int value) =>
_prefs.setInt(key, value);
bool? getBool(String key) => _prefs.getBool(key);
Future<bool> setBool(String key, bool value) =>
_prefs.setBool(key, value);
Future<bool> clear() => _prefs.clear();
}
// В main
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Инициализируем до runApp
await PreferencesManager.initialize();
runApp(MyApp());
}
// Использование в любом месте
class AuthService {
final _prefs = PreferencesManager();
Future<void> saveToken(String token) async {
await _prefs.setString('auth_token', token);
}
String? getToken() => _prefs.getString('auth_token');
}
Пример: Logger Singleton
// ✅ Простой Logger Singleton
class Logger {
static final Logger _instance = Logger._internal();
late final File _logFile;
final List<String> _logs = [];
Logger._internal() {
_initializeLogFile();
}
factory Logger() => _instance;
void _initializeLogFile() {
print('Logger initialized');
}
void log(String message) {
final timestamp = DateTime.now().toIso8601String();
final logMessage = '[$timestamp] $message';
_logs.add(logMessage);
print(logMessage);
// Можно сохранять в файл
_saveToFile(logMessage);
}
void _saveToFile(String message) {
// Реализация сохранения в файл
}
List<String> getAllLogs() => List.unmodifiable(_logs);
void clearLogs() {
_logs.clear();
}
}
// Использование
void main() {
Logger().log('Application started');
Logger().log('User logged in');
Logger().log('Fetching data...');
// Одно и то же значение
print(identical(Logger(), Logger())); // true
}
Пример: Theme Manager Singleton
// ✅ Управление темой приложения
class ThemeManager {
static final ThemeManager _instance = ThemeManager._internal();
ThemeMode _currentTheme = ThemeMode.light;
final List<VoidCallback> _listeners = [];
ThemeManager._internal();
factory ThemeManager() => _instance;
ThemeMode get currentTheme => _currentTheme;
void setTheme(ThemeMode theme) {
if (_currentTheme != theme) {
_currentTheme = theme;
_notifyListeners();
}
}
void toggleTheme() {
_currentTheme = _currentTheme == ThemeMode.light
? ThemeMode.dark
: ThemeMode.light;
_notifyListeners();
}
void addListener(VoidCallback callback) {
_listeners.add(callback);
}
void removeListener(VoidCallback callback) {
_listeners.remove(callback);
}
void _notifyListeners() {
for (final listener in _listeners) {
listener();
}
}
}
// Использование в MyApp
class MyApp extends StatefulWidget {
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late VoidCallback _themeListener;
@override
void initState() {
super.initState();
_themeListener = () => setState(() {});
ThemeManager().addListener(_themeListener);
}
@override
void dispose() {
ThemeManager().removeListener(_themeListener);
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
themeMode: ThemeManager().currentTheme,
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
home: HomePage(),
);
}
}
Когда использовать Singleton?
// ✅ ИСПОЛЬЗУЙ Singleton для:
// 1. Database подключение
class Database {
static final Database _instance = Database._internal();
// ...
}
// 2. Network клиент
class HttpClient {
static final HttpClient _instance = HttpClient._internal();
// ...
}
// 3. Логирование
class Logger {
static final Logger _instance = Logger._internal();
// ...
}
// 4. Конфигурация приложения
class AppConfig {
static final AppConfig _instance = AppConfig._internal();
// ...
}
// 5. Управление состоянием (shared state)
class UserSession {
static final UserSession _instance = UserSession._internal();
// ...
}
Когда НЕ использовать Singleton?
// ❌ НЕ используй Singleton для:
// 1. Моделей данных (User, Product, etc)
// Может быть много экземпляров
class User {
final int id;
final String name;
User(this.id, this.name);
}
// 2. Widget'ов
class MyButton extends StatelessWidget {}
// 3. Repository'ев (используй dependency injection)
class UserRepository {
final ApiClient _api;
UserRepository(this._api);
}
Thread-safety (важно!)
// ✅ Dart однопоточный, но если используешь Isolate'ы:
class SafeSingleton {
static SafeSingleton? _instance;
static final _lock = Mutex(); # Для многопоточности
SafeSingleton._internal();
factory SafeSingleton() {
// В Dart обычно не нужно (однопоточный)
_instance ??= SafeSingleton._internal();
return _instance!;
}
}
Резюме
Singleton паттерн:
- Гарантирует один экземпляр — всегда один инстанс
- Глобальный доступ — можно получить отовсюду
- Инициализация один раз — экономит ресурсы
Способы реализации:
- ✅ Классический factory конструктор
- ✅ С lazy инициализацией
- ✅ С явной инициализацией (лучше)
- ✅ С GetIt (для больших проектов)
Когда использовать:
- Database подключение
- Network клиент
- Logger
- Configuration
- Session управление
Главное правило: Используй Singleton только когда действительно нужен один экземпляр на всё приложение!