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

Какие паттерны проектирования не нравятся

3.0 Senior🔥 141 комментариев
#SOLID и паттерны проектирования

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Критический взгляд на паттерны проектирования

За 10+ лет разработки я научился критически относиться к паттернам проектирования. Они — не панацея, а инструменты, которые нужно применять с осторожностью. Расскажу о паттернах, которые, на мой взгляд, часто используются неправильно или приносят больше вреда, чем пользы.

1. Singleton — самый опасный паттерн

Singleton часто критикуют, и в этом есть очень веские причины.

// Классический Singleton
public class DatabaseConnection {
    private static DatabaseConnection instance;
    
    private DatabaseConnection() {}
    
    public static synchronized DatabaseConnection getInstance() {
        if (instance == null) {
            instance = new DatabaseConnection();
        }
        return instance;
    }
}

// Где это создает проблемы:
// 1. Скрытые зависимости
public class UserService {
    // Зависимость от DatabaseConnection замаскирована
    public void createUser(String name) {
        DatabaseConnection db = DatabaseConnection.getInstance();
        // ...
    }
}

// 2. Невозможно тестировать (сложно мокировать)
// 3. Потокобезопасность — часто реализована неправильно
// 4. Нарушение принципа Single Responsibility

Почему не нравится:

  • Скрытые зависимости — код получает зависимость не через конструктор
  • Сложность тестирования — нельзя легко подменить на mock
  • Глобальное состояние — нарушает принципы FP и чистоты функций
  • Потокобезопасность — обычно реализуется неправильно

Что использовать вместо Singleton:

// Инъекция зависимостей (правильный подход)
public class UserService {
    private final DatabaseConnection db;
    
    // Зависимость явно указана в конструкторе
    public UserService(DatabaseConnection db) {
        this.db = db;
    }
    
    public void createUser(String name) {
        db.executeQuery("INSERT INTO users...");
    }
}

// В Spring это просто
@Service
public class UserService {
    @Autowired
    private DatabaseConnection db;  // Spring управляет жизненным циклом
}

2. Абстрактная фабрика — за-инженеринг

Этот паттерн часто используется, когда вообще не нужен.

// Абстрактная фабрика - перебор
interface UIFactory {
    Button createButton();
    TextField createTextField();
    CheckBox createCheckBox();
}

class WindowsUIFactory implements UIFactory {
    public Button createButton() { return new WindowsButton(); }
    public TextField createTextField() { return new WindowsTextField(); }
    public CheckBox createCheckBox() { return new WindowsCheckBox(); }
}

// Проблемы:
// 1. Много бойлерплейта
// 2. Сложно добавлять новые методы (нужно менять все фабрики)
// 3. Часто используется где-то, где достаточно простой фабрики

Почему не нравится:

  • Оверинженеринг — используется где простой фабрики достаточно
  • Жесткость — добавление новых методов требует изменения всех реализаций
  • Много кода — большое количество бойлерплейта

Альтернатива (с Java 8+):

// Функциональный подход - гораздо проще
public class UIFactory {
    private Supplier<Button> buttonCreator;
    private Supplier<TextField> textFieldCreator;
    
    public UIFactory(
        Supplier<Button> buttonCreator,
        Supplier<TextField> textFieldCreator
    ) {
        this.buttonCreator = buttonCreator;
        this.textFieldCreator = textFieldCreator;
    }
    
    public Button createButton() { return buttonCreator.get(); }
    public TextField createTextField() { return textFieldCreator.get(); }
}

// Использование
UIFactory factory = new UIFactory(
    () -> new WindowsButton(),
    () -> new WindowsTextField()
);

3. Fluent Builder — красиво, но сложно

// Fluent Builder
public class QueryBuilder {
    private String table;
    private List<String> columns = new ArrayList<>();
    private List<String> conditions = new ArrayList<>();
    
    public QueryBuilder from(String table) {
        this.table = table;
        return this;
    }
    
    public QueryBuilder select(String... cols) {
        columns.addAll(Arrays.asList(cols));
        return this;
    }
    
    public QueryBuilder where(String condition) {
        conditions.add(condition);
        return this;
    }
    
    public String build() {
        // Сложная логика построения запроса
        return "SELECT " + String.join(", ", columns) + " FROM " + table;
    }
}

// Использование
String sql = new QueryBuilder()
    .from("users")
    .select("id", "name", "email")
    .where("age > 18")
    .where("country = 'USA'")
    .build();

// Проблемы:
// 1. Можно забыть вызвать build() — нет гарантий
// 2. Невозможна валидация на этапе построения
// 3. Может быть очень много методов в цепи

Почему не нравится:

  • Нет типобезопасности — можно создать невалидный объект
  • Сложность отладки — цепь вызовов может быть длинной
  • Мутирует состояние — промежуточные объекты меняются

Альтернатива (конструктор с параметрами):

// Просто используй конструктор
public class Query {
    private final String table;
    private final List<String> columns;
    private final List<String> conditions;
    
    public Query(String table, List<String> columns, List<String> conditions) {
        this.table = table;
        this.columns = columns;
        this.conditions = conditions;
    }
}

// Или record в Java 16+
public record Query(
    String table,
    List<String> columns,
    List<String> conditions
) {}

4. Service Locator — скрытые зависимости

// Service Locator - антипаттерн
public class ServiceLocator {
    private static Map<Class<?>, Object> services = new HashMap<>();
    
    public static void register(Class<?> service, Object instance) {
        services.put(service, instance);
    }
    
    public static <T> T getService(Class<T> service) {
        return (T) services.get(service);
    }
}

// Использование (плохо)
public class UserService {
    public void createUser(String name) {
        DatabaseConnection db = ServiceLocator.getService(DatabaseConnection.class);
        // Неявная зависимость!
    }
}

// Проблемы:
// 1. Скрытые зависимости (как Singleton)
// 2. Сложно тестировать
// 3. Код неясен — не видно, какие зависимости нужны

Почему не нравится:

  • Скрытые зависимости — невозможно понять из сигнатуры метода, что нужно
  • Runtime ошибки — зависимость не найдена только во время выполнения
  • Сложность тестирования — нужно регистрировать все в Service Locator

5. GoF паттерны которые устарели

Observer (до Java 8)

// Старый способ (Observer паттерн из GoF)
interface Observer {
    void update(Observable o, Object arg);
}

class ConcreteObserver implements Observer {
    public void update(Observable o, Object arg) {
        System.out.println("Событие: " + arg);
    }
}

// Современный способ (функциональный)
observable.subscribe(
    event -> System.out.println("Событие: " + event)
);

Strategy (до Java 8)

// Старый способ (Strategy паттерн)
interface SortStrategy {
    void sort(List<?> items);
}

class QuickSortStrategy implements SortStrategy {
    public void sort(List<?> items) { /* ... */ }
}

class Sorter {
    private SortStrategy strategy;
    Sorter(SortStrategy strategy) { this.strategy = strategy; }
    void sort(List<?> items) { strategy.sort(items); }
}

// Современный способ (функциональный)
List<Integer> items = Arrays.asList(3, 1, 2);
items.sort((a, b) -> Integer.compare(a, b));  // Strategy как lambda

6. Template Method — жесткая иерархия

// Template Method - часто приводит к жесткой иерархии
abstract class ReportGenerator {
    public final void generate() {
        collectData();
        formatData();
        sendReport();
    }
    
    protected abstract void collectData();
    protected abstract void formatData();
    protected abstract void sendReport();
}

class PDFReportGenerator extends ReportGenerator {
    @Override
    protected void collectData() { }
    @Override
    protected void formatData() { }
    @Override
    protected void sendReport() { }
}

// Проблемы:
// 1. Жесткая иерархия (наследование)
// 2. Сложно комбинировать различные шаги
// 3. Нарушает composition over inheritance

// Функциональный подход (лучше)
public class ReportGenerator {
    private Consumer<List<?>> dataCollector;
    private Function<List<?>, String> formatter;
    private Consumer<String> sender;
    
    public void generate() {
        List<?> data = new ArrayList<>();
        dataCollector.accept(data);
        String formatted = formatter.apply(data);
        sender.accept(formatted);
    }
}

7. Decorator — может стать слишком сложным

// Decorator может быть невероятно сложным
InputStream input = new GZIPInputStream(
    new BufferedInputStream(
        new FileInputStream("file.txt")
    )
);

// Проблемы:
// 1. Много слоев обёртывания
// 2. Сложно отследить что где происходит
// 3. Порядок Decorator'ов критичен

// Альтернатива (if возможна) - конфигурация
public class InputStreamConfig {
    private boolean gzipped = false;
    private boolean buffered = false;
    
    public InputStream build(String filename) throws IOException {
        InputStream stream = new FileInputStream(filename);
        if (buffered) stream = new BufferedInputStream(stream);
        if (gzipped) stream = new GZIPInputStream(stream);
        return stream;
    }
}

Рекомендации

  1. Не используй паттерны "потому что они есть" — используй потому что они решают реальную проблему
  2. Предпочитай композицию наследованию — это часто заменяет множество паттернов
  3. Используй функциональное программирование — Java 8+ позволяет избежать много паттернов
  4. Тестируемость > проектирование — если код сложно тестировать, это плохой паттерн
  5. KISS (Keep It Simple, Stupid) — самое важное правило

Что использовать вместо этих паттернов

// Dependency Injection вместо Singleton
// Composition вместо Inheritance
// Functional interfaces вместо Strategy/Observer
// Constructor параметры вместо Service Locator
// Spring/Guice вместо ручного управления зависимостями

Заключение

Паттерны проектирования — это инструменты, а не закон. Лучшие разработчики знают когда использовать паттерны, когда нарушать их, и когда просто писать простой, понятный код. YAGNI (You Aren't Gonna Need It) и KISS часто важнее любого паттерна.