← Назад к вопросам

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

1.0 Junior🔥 181 комментариев
#ООП и паттерны

Комментарии (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 паттерн:

  1. Гарантирует один экземпляр — всегда один инстанс
  2. Глобальный доступ — можно получить отовсюду
  3. Инициализация один раз — экономит ресурсы

Способы реализации:

  • ✅ Классический factory конструктор
  • ✅ С lazy инициализацией
  • ✅ С явной инициализацией (лучше)
  • ✅ С GetIt (для больших проектов)

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

  • Database подключение
  • Network клиент
  • Logger
  • Configuration
  • Session управление

Главное правило: Используй Singleton только когда действительно нужен один экземпляр на всё приложение!