← Назад к вопросам
Singleton: паттерн или антипаттерн
2.0 Middle🔥 181 комментариев
#SOLID и паттерны проектирования
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Singleton: паттерн или антипаттерн?
Ответ: Оба верны. Singleton — это валидный паттерн проектирования для специфических случаев, но часто используется неправильно и становится антипаттерном. Ключ в том, когда его применять.
Когда Singleton — паттерн (уместен)
// 1. Конфигурация приложения - должна быть одна
public class ApplicationConfig {
private static ApplicationConfig instance;
private Properties properties;
private ApplicationConfig() {
properties = new Properties();
// загрузка конфигурации
}
public static synchronized ApplicationConfig getInstance() {
if (instance == null) {
instance = new ApplicationConfig();
}
return instance;
}
public String getProperty(String key) {
return properties.getProperty(key);
}
}
// Использование: одна конфигурация на всё приложение
// 2. Логирование - обычно одна система логирования
public class Logger {
private static final Logger instance = new Logger();
private PrintWriter writer;
private Logger() {
try {
writer = new PrintWriter(new FileWriter("app.log"));
} catch (IOException e) {
e.printStackTrace();
}
}
public static Logger getInstance() {
return instance;
}
public void log(String message) {
writer.println(message);
}
}
// Единая точка логирования
// 3. Пул подключений к БД - обычно один пул
public class DatabaseConnectionPool {
private static final DatabaseConnectionPool instance = new DatabaseConnectionPool();
private final List<Connection> availableConnections = new ArrayList<>();
private final List<Connection> usedConnections = new ArrayList<>();
private DatabaseConnectionPool() {
// инициализация пула подключений
}
public static DatabaseConnectionPool getInstance() {
return instance;
}
public synchronized Connection getConnection() {
// логика получения подключения
return null;
}
}
Когда Singleton — антипаттерн (не уместен)
1. Нарушает принцип Single Responsibility:
// Плохо: Singleton отвечает за создание И бизнес-логику
public class UserService {
private static final UserService instance = new UserService();
private UserRepository repository;
public static UserService getInstance() {
return instance;
}
public User findUser(int id) {
return repository.find(id);
}
}
// Хорошо: Dependency Injection
public class UserService {
private final UserRepository repository;
public UserService(UserRepository repository) {
this.repository = repository;
}
public User findUser(int id) {
return repository.find(id);
}
}
// Используется в контексте DI контейнера (Spring, Guice)
2. Затрудняет тестирование:
// Сложно тестировать
public class PaymentProcessor {
private static final PaymentProcessor instance = new PaymentProcessor();
private PaymentGateway gateway = new RealPaymentGateway(); // real!
public static PaymentProcessor getInstance() {
return instance;
}
public boolean processPayment(double amount) {
return gateway.charge(amount);
}
}
// Тест не может использовать mock
@Test
public void testPayment() {
PaymentProcessor processor = PaymentProcessor.getInstance();
// gateway это реальный объект, невозможно захватить
assertThat(processor.processPayment(100)).isTrue();
}
// Правильный подход
public class PaymentProcessor {
private final PaymentGateway gateway;
public PaymentProcessor(PaymentGateway gateway) {
this.gateway = gateway;
}
public boolean processPayment(double amount) {
return gateway.charge(amount);
}
}
@Test
public void testPayment() {
PaymentGateway mockGateway = mock(PaymentGateway.class);
when(mockGateway.charge(100)).thenReturn(true);
PaymentProcessor processor = new PaymentProcessor(mockGateway);
assertThat(processor.processPayment(100)).isTrue();
}
3. Скрывает зависимости:
// Плохо: зависимость скрыта в методе
public class OrderService {
public void createOrder(Order order) {
// откуда берется DatabaseConnectionPool? Не понятно!
Connection conn = DatabaseConnectionPool.getInstance().getConnection();
// сохраняем заказ
}
}
// Хорошо: зависимость явна
public class OrderService {
private final DatabaseConnectionPool pool;
public OrderService(DatabaseConnectionPool pool) {
this.pool = pool;
}
public void createOrder(Order order) {
Connection conn = pool.getConnection();
// сохраняем заказ
}
}
Правильные реализации Singleton
Eager initialization (потокобезопасно изначально):
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
Lazy initialization с double-checked locking:
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
Bill Pugh Singleton (лучший подход):
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
// Потокобезопасен благодаря classloader
// Ленива инициализация
Enum Singleton (самый безопасный):
public enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("Doing something");
}
}
// Использование
Singleton.INSTANCE.doSomething();
// Защищен от:
// - Reflexon (не может создать новый экземпляр)
// - Serialization (гарантирует один экземпляр после десериализации)
// - Threading (потокобезопасен по умолчанию)
Вывод
Singleton — паттерн, когда:
- Логически существует только один экземпляр (конфиг, логирование)
- Явно требуется глобальная точка доступа
- Создание объекта дорого
Singleton — антипаттерн, когда:
- Его можно заменить Dependency Injection
- Это скрывает зависимости
- Это затрудняет тестирование
- В Spring контексте — используй
@Beanс scope singleton вместо ручного Singleton
Современный подход: избегай Singleton в пользу DI контейнеров (Spring, Guice). Они управляют жизненным циклом объектов и scope, скрывая детали от разработчика.