Обсудите основные паттерны проектирования (Factory, Singleton, Observer, Strategy) и приведите пример, когда применение паттерна ухудшает архитектуру вместо улучшения
Кратко по четырём паттернам — назначение, когда применять, основные плюсы/минусы. Factory - Назначение: скрыть создание объектов (абстрагировать конкретные классы), выбирать конкретную реализацию во время выполнения. - Когда применять: когда код зависит от абстракций, а конкретные реализации меняются или выбираются в рантайме; при тестировании — легко подставить заглушку. - Плюсы: убирает привязку к конкретным классам, централизует логику создания, упрощает расширение. - Минусы/подводные камни: излишняя фабрикация для простых случаев добавляет уровень абстракции и усложняет чтение; фабрики не нужны, если зависимость простая или объект создаётся в одном месте. Singleton - Назначение: гарантировать единственный экземпляр класса и глобальную точку доступа. - Когда применять: редкие случаи — кэш/фабричный ресурс с действительно единственным экземпляром в процессе. - Плюсы: простота доступа к общему ресурсу. - Минусы: глобальное состояние, скрытые зависимости, сложное тестирование (трудно мокать/сбрасывать), проблемы с потокобезопасностью, препятствует инверсии управления. Observer - Назначение: уведомлять подписчиков об изменениях состояния издателя (publish–subscribe). - Когда применять: когда нужно разделить источник событий и обработчики, поддерживать динамическое добавление/удаление слушателей. - Плюсы: слабая связанность между компонентами, удобно для UI/событийных систем. - Минусы: неявный поток управления (трудно отследить порядок вызовов), возможны утечки памяти (если не отписываться), сложности с транзакционностью и отладкой. Strategy - Назначение: инкапсулировать алгоритм в отдельный класс и сделать их взаимозаменяемыми. - Когда применять: когда есть семейство алгоритмов, которые должны меняться независимо от клиентов. - Плюсы: делает поведение гибким, упрощает тестирование алгоритмов. - Минусы: может породить множество мелких классов и усложнить конфигурацию; иногда проще передать функцию/замыкание. Пример, когда паттерн ухудшает архитектуру - Сценарий: веб-приложение, где во всех местах доступа к базе данных и конфигурации используется Singleton (статический Singletons для DB-коннекта, конфигурации, логгера). - Почему это ухудшает архитектуру: Singletons превращаются в глобальное состояние и скрытые зависимости — функции и классы получают доступ к ресурсам не через явные параметры/инъекции, а через глобальные обращения. Это осложняет модульное тестирование (нужны глобальные подмены/сбросы), делает код жёстко связанным, плохо работает в средах с несколькими конфигурациями/тенантами, приводит к проблемам инициализации и потокобезопасности. - Альтернатива: передавать зависимости через конструкторы/фабрики или использовать DI-контейнер; если нужен один экземпляр — реализовать его через scoped lifetime в DI, а не через глобальный статический Singleton. Короткие дополнительные примеры плохого применения: - Factory там, где объект прост и создаётся локально — добавляет лишнюю индикацию и усложняет код. - Observer в бизнес-логике вместо явных вызовов — приводит к «невидимому» потоку управления и сложной отладке. - Strategy при небольшом количествe вариантов — множество классов ради пары строк кода. Вывод: паттерны полезны при соблюдении инвариантов, но их чрезмерное или неуместное применение (особенно Singleton и неоправданная фабрикация/событность) ухудшает читаемость, тестируемость и надёжность.
Factory
- Назначение: скрыть создание объектов (абстрагировать конкретные классы), выбирать конкретную реализацию во время выполнения.
- Когда применять: когда код зависит от абстракций, а конкретные реализации меняются или выбираются в рантайме; при тестировании — легко подставить заглушку.
- Плюсы: убирает привязку к конкретным классам, централизует логику создания, упрощает расширение.
- Минусы/подводные камни: излишняя фабрикация для простых случаев добавляет уровень абстракции и усложняет чтение; фабрики не нужны, если зависимость простая или объект создаётся в одном месте.
Singleton
- Назначение: гарантировать единственный экземпляр класса и глобальную точку доступа.
- Когда применять: редкие случаи — кэш/фабричный ресурс с действительно единственным экземпляром в процессе.
- Плюсы: простота доступа к общему ресурсу.
- Минусы: глобальное состояние, скрытые зависимости, сложное тестирование (трудно мокать/сбрасывать), проблемы с потокобезопасностью, препятствует инверсии управления.
Observer
- Назначение: уведомлять подписчиков об изменениях состояния издателя (publish–subscribe).
- Когда применять: когда нужно разделить источник событий и обработчики, поддерживать динамическое добавление/удаление слушателей.
- Плюсы: слабая связанность между компонентами, удобно для UI/событийных систем.
- Минусы: неявный поток управления (трудно отследить порядок вызовов), возможны утечки памяти (если не отписываться), сложности с транзакционностью и отладкой.
Strategy
- Назначение: инкапсулировать алгоритм в отдельный класс и сделать их взаимозаменяемыми.
- Когда применять: когда есть семейство алгоритмов, которые должны меняться независимо от клиентов.
- Плюсы: делает поведение гибким, упрощает тестирование алгоритмов.
- Минусы: может породить множество мелких классов и усложнить конфигурацию; иногда проще передать функцию/замыкание.
Пример, когда паттерн ухудшает архитектуру
- Сценарий: веб-приложение, где во всех местах доступа к базе данных и конфигурации используется Singleton (статический Singletons для DB-коннекта, конфигурации, логгера).
- Почему это ухудшает архитектуру: Singletons превращаются в глобальное состояние и скрытые зависимости — функции и классы получают доступ к ресурсам не через явные параметры/инъекции, а через глобальные обращения. Это осложняет модульное тестирование (нужны глобальные подмены/сбросы), делает код жёстко связанным, плохо работает в средах с несколькими конфигурациями/тенантами, приводит к проблемам инициализации и потокобезопасности.
- Альтернатива: передавать зависимости через конструкторы/фабрики или использовать DI-контейнер; если нужен один экземпляр — реализовать его через scoped lifetime в DI, а не через глобальный статический Singleton.
Короткие дополнительные примеры плохого применения:
- Factory там, где объект прост и создаётся локально — добавляет лишнюю индикацию и усложняет код.
- Observer в бизнес-логике вместо явных вызовов — приводит к «невидимому» потоку управления и сложной отладке.
- Strategy при небольшом количествe вариантов — множество классов ради пары строк кода.
Вывод: паттерны полезны при соблюдении инвариантов, но их чрезмерное или неуместное применение (особенно Singleton и неоправданная фабрикация/событность) ухудшает читаемость, тестируемость и надёжность.