Приведи пример проекта где использовал шаблон проектирования
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Отличный вопрос. Я действительно много раз применял различные шаблоны проектирования (Design Patterns) на практике. В качестве наиболее показательного примера приведу компонент модального окна/диалога в большом SPA-приложении на React (или Vue/Angular), который мы разрабатывали. Этот компонент идеально иллюстрирует целый ряд паттернов.
Контекст проекта и проблема
В приложении для управления проектами (аналог Jira или Trello) существовала острая необходимость в единой, надежной и гибкой системе отображения модальных окон, диалогов подтверждения, форм создания/редактирования задач, боковых панелей и т.д. Перед командой стояли задачи:
- Избежать дублирования логики открытия/закрытия.
- Централизовать управление состоянием видимости и историей (например, для стека модальных окон).
- Сделать API для вызова модалок из любого места приложения (компонентов, хуков, сторов, не только по клику на кнопку).
- Обеспечить легкую замену UI-обертки (скругления, тени, анимации) для всех диалогов.
- Упростить тестирование.
Для решения мы применили комбинацию нескольких классических паттернов.
Реализация и использованные паттерны
1. Singleton (Одиночка) — для сервиса управления модалками
Нам нужна была единая точка управления. Мы создали класс ModalService, экземпляр которого был доступен глобально (через контекст React или DI в Vue). Это гарантировало, что состояние всех модалок координируется из одного места.
// Типизированный сервис, реализующий паттерн Singleton через модуль или контекст
class ModalService {
private static instance: ModalService;
private modals: Map<string, IModalComponent> = new Map();
private listeners: Set<(modals: IModalData[]) => void> = new Set();
private constructor() {}
static getInstance(): ModalService {
if (!ModalService.instance) {
ModalService.instance = new ModalService();
}
return ModalService.instance;
}
open(modalId: string, component: React.ComponentType, props: any) {
this.modals.set(modalId, { component, props });
this.notifyListeners();
}
close(modalId: string) {
this.modals.delete(modalId);
this.notifyListeners();
}
// ... методы подписки (subscribe/notify)
}
2. Factory Method (Фабричный метод) — для создания конфигураций модалок
Вместо того чтобы вручную прописывать type, title, buttons для каждого диалога подтверждения, мы создали фабричные методы.
// Фабрика предопределенных диалогов
class DialogFactory {
static createConfirmDialog(
message: string,
onConfirm: () => void
): IModalConfig {
return {
component: ConfirmDialog,
props: {
message,
onConfirm,
variant: 'danger'
},
overlayClose: false
};
}
static createFormDialog<T>(formComponent: React.ComponentType<T>, initialData: T): IModalConfig {
return {
component: formComponent,
props: { initialData },
overlayClose: true,
size: 'large'
};
}
}
// Использование в компоненте:
const handleDelete = () => {
const config = DialogFactory.createConfirmDialog(
"Удалить задачу безвозвратно?",
() => api.deleteTask(taskId)
);
ModalService.getInstance().open('confirm-delete', config.component, config.props);
};
3. Observer (Наблюдатель) — для реактивности
Компонент-контейнер ModalRoot, который рендерит все активные модалки, должен был реагировать на изменения в ModalService. Это классический Observer: сервис (Subject) уведомляет подписчиков (Observers, в нашем случае ModalRoot) об изменениях в коллекции активных модалок.
// Компонент-наблюдатель (React)
const ModalRoot: React.FC = () => {
const [activeModals, setActiveModals] = useState<IModalData[]>([]);
useEffect(() => {
const handleUpdate = (modals: IModalData[]) => setActiveModals(modals);
const subscriptionId = ModalService.subscribe(handleUpdate);
return () => ModalService.unsubscribe(subscriptionId);
}, []);
return (
<Portal> {/* Используется Portal для рендера вне основного дерева */}
{activeModals.map(({ id, component: Component, props }) => (
<ModalWrapper key={id}> {/* Единая обертка */}
<Component {...props} />
</ModalWrapper>
))}
</Portal>
);
};
4. Compound Components (Составные компоненты) — для гибкости UI
Для построения сложных модальных окон (например, многошаговая форма) мы использовали подход Compound Components (паттерн, популярный в React-экосистеме). Это позволило гибко комбинировать содержимое.
// Использование составного компонента
<ComplexModal>
<Modal.Header title="Создание проекта" />
<Modal.Body>
<FormStepper steps={['Основное', 'Настройки', 'Команда']} />
</Modal.Body>
<Modal.Footer>
<Button variant="secondary">Назад</Button>
<Button variant="primary">Далее</Button>
</Modal.Footer>
</ComplexModal>
5. Strategy (Стратегия) — для различных анимаций открытия/закрытия
Анимации (fade, slide-up, scale) были инкапсулированы в стратегии. Компонент ModalWrapper принимал animationStrategy, что позволяло легко добавлять новые типы анимаций.
interface AnimationStrategy {
enter: (element: HTMLElement) => Promise<void>;
leave: (element: HTMLElement) => Promise<void>;
}
const fadeStrategy: AnimationStrategy = {
enter: (el) => { /* реализация fade-in */ },
leave: (el) => { /* реализация fade-out */ }
};
const slideUpStrategy: AnimationStrategy = { /* ... */ };
Итоги и выгоды
Использование этой связки паттернов привело к значительным преимуществам:
- Снижение связности: Компоненты бизнес-логики больше не знали, как рендерится модалка, они только запрашивали ее через сервис.
- Повторное использование: Фабричные методы и единая обертка (
ModalWrapper) устранили дублирование кода. - Удобство тестирования:
ModalServiceи фабрики можно было тестировать изолированно, мокая их в юнит-тестах компонентов. - Гибкость и масштабируемость: Добавление новой модалки сводилось к созданию компонента содержимого и вызову фабрики или сервиса. Легко было внедрить логику "предотвращения потери данных при закрытии".
- Согласованность UI: Все диалоги имели одинаковое поведение (закрытие по ESC, клику на оверлей) и внешний вид благодаря единому корневому компоненту.
Этот проект стал для команды наглядным учебным пособием о том, как абстрактные шаблоны из книг превращаются в конкретные, поддерживаемые и мощные архитектурные решения в реальном production-коде. Ключ был не в слепом следовании паттернам, а в их осмысленном применении для решения конкретных проблем проектирования.