JavaRush /Курсы /Design Patterns, SQL, и Docker /Структурные паттерны

Структурные паттерны

Design Patterns, SQL, и Docker
1 уровень , 1 лекция
Открыта

Adapter

Адаптер — структурный паттерн, который позволяет объектам с несовместимыми интерфейсами работать вместе. Это «переходник» между тем, что вам прислали, и тем, что вам нужно.

Во Frontend-разработке это, пожалуй, самый частый паттерн. Бэкенд (особенно если это Legacy) часто отдает данные в формате, который неудобен для UI. Вместо того чтобы переписывать компоненты под странный формат API, мы пишем функцию-адаптер.

    flowchart LR
        UI["React Component"] -- "Expects: { id, title }" --> Adapter["Data Adapter"]
        Adapter -- "Transforms Data" --> Backend["Legacy API"]
        Backend -- "Returns: { ID_VAL, t_text }" --> Adapter
        Adapter -- "Returns: { id, title }" --> UI
    

Сильные стороны:

  • Чистый код компонентов: Ваш UI не знает про странные поля типа user_id_v2 с бэкенда. Он работает с чистым userId.
  • Легкий рефакторинг: Если бэкенд изменит API, вы поправите только Адаптер, не трогая сотни компонентов.

Нормализация данных

Представьте, что вы получаете список пользователей. Бэкенд шлет дату рождения строкой, а имя и фамилию — разными полями. Компонент же хочет готовое имя и объект Date.


// 1. Данные от сервера (неудобные)
const apiResponse = {
    user_id_val: 101,
    first_name: "John",
    last_name: "Doe",
    dob: "1990-01-01"
};

// 2. Адаптер (Чистая функция)
const adaptUser = (serverData) => ({
    id: serverData.user_id_val,
    fullName: `${serverData.first_name} ${serverData.last_name}`,
    birthDate: new Date(serverData.dob),
    isAdmin: false // Дефолтное значение, которого нет в API
});

// 3. Использование в UI
const user = adaptUser(apiResponse);
console.log(user.fullName); // "John Doe"

Decorator

Декоратор (Decorator) — паттерн, позволяющий динамически добавлять объекту (или функции) новую функциональность, оборачивая его в полезную «обертку».

Принцип «Матрешки»

    graph TD
        Client --> Decorator["Decorator (Logging)"]
        Decorator --> Component["Core Component (Button)"]

        style Decorator fill:#f9f,stroke:#333
        style Component fill:#ccf,stroke:#333
    

В объектно-ориентированном программировании декораторы расширяют классы. В функциональном JS и React декораторы — это функции высшего порядка (HOC) или просто обертки.

Higher-Order Components (HOC)

В мире React этот паттерн долгое время был стандартом (сейчас его вытесняют хуки, но принцип обертки остался). Допустим, у нас есть обычная кнопка, и мы хотим добавить ей логику аналитики, не меняя код кнопки.


// Базовый компонент
const SimpleButton = (props) => {
    return <button onClick={props.onClick}>{props.label}</button>;
};

// Декоратор (функция-обертка)
const withAnalytics = (Component) => {
    return (props) => {
        const handleClick = () => {
            console.log("Analytics: Button clicked!"); // Добавленная логика
            if (props.onClick) props.onClick();
        };
        // Рендерим исходный компонент с подмененным onClick
        return <Component {...props} onClick={handleClick} />;
    };
};

// Использование
const AnalyticsButton = withAnalytics(SimpleButton);
Важное примечание:

В JavaScript/TypeScript (и особенно в Angular) есть специальный синтаксис декораторов через @.

@Component({ selector: 'app-root' })
class App {}

Суть та же: мы "навешиваем" на класс дополнительную логику (метаданные), не меняя код самого класса. React предпочитает функции-обертки (HOC), а Angular — синтаксис @.

Composite

Компоновщик — позволяет сгруппировать объекты в древовидную структуру и работать с этой структурой так же, как с одиночным объектом.

Дерево интерфейса

    graph TD
        Root[App Container]
        Header[Header]
        Main[Main Content]
        Footer[Footer]

        Root --> Header
        Root --> Main
        Root --> Footer

        Main --> Sidebar
        Main --> Feed
        Feed --> Post1
        Feed --> Post2
    

Это самый родной паттерн для веба. DOM-дерево браузера — это Composite. <div> может содержать другие <div> или текст. React-дерево — это Composite. Группы слоев в Figma — это Composite.

Web-way Reality: Рекурсивный рендеринг

Паттерн проявляется, когда нам нужно отрисовать вложенную структуру, не зная заранее глубины вложенности.


// Компонент, который может содержать сам себя
const Folder = ({ name, files }) => (
    <div>
        <span>📁 {name}</span>
        <div style={{ paddingLeft: 20 }}>
            {files.map(file =>
                file.type === 'folder'
                    ? <Folder key={file.name} {...file} /> // Рекурсия (Composite)
                    : <span key={file.name}>📄 {file.name}</span>
            )}
        </div>
    </div>
);

Proxy

Заместитель — объект, который перехватывает вызовы к другому объекту. Это «секьюрити» на входе в клуб: он может не пустить (валидация), может что-то записать в журнал (логирование), а может просто передать запрос дальше.

Перехват доступа

    sequenceDiagram
        participant Client
        participant Proxy
        participant RealSubject as Real Data

        Client->>Proxy: Request Data (get ID: 1)
        Note over Proxy: Check Cache?
        alt Cache hit
            Proxy-->>Client: Return Cached Data
        else Cache miss
            Proxy->>RealSubject: Fetch Data
            RealSubject-->>Proxy: Data
            Proxy-->>Client: Data
        end
    

JS Proxy и Реактивность

В современном JavaScript есть встроенный объект Proxy. Именно на нем построена реактивность во Vue 3 и MobX. Когда вы меняете свойство объекта, Proxy перехватывает это изменение и запускает перерисовку интерфейса.


const user = {
    name: "Alice",
    age: 25
};

// Создаем прокси для валидации
const secureUser = new Proxy(user, {
    set(target, prop, value) {
        if (prop === 'age' && typeof value !== 'number') {
            console.error("Age must be a number!");
            return false;
        }
        console.log(`Property ${prop} changed to ${value}`);
        target[prop] = value;
        return true;
    }
});

secureUser.age = "text"; // Ошибка: Age must be a number!
secureUser.age = 26;     // Лог: Property age changed to 26

Bridge

Мост — разделяет абстракцию (интерфейс) и реализацию, чтобы они могли изменяться независимо. Это паттерн для проектирования библиотек и кросс-платформенных решений.

Разделение UI и Платформы

    graph TD
        subgraph Abstraction ["UI Library"]
            Button["Button Component"]
        end

        subgraph Implementation ["Renderer"]
            Web["Web Renderer (DOM)"]
            Mobile["Mobile Renderer (Native)"]
        end

        Button -.->|Uses| Web
        Button -.->|Uses| Mobile
    

В веб-разработке чистый "Мост" встречается редко в бизнес-логике, но часто в архитектуре. Самый яркий пример — React. Сами компоненты React (Абстракция) не знают, где они будут отрисованы. За отрисовку отвечает react-dom или react-native.

Темы и UI Киты

Более приземленный пример — система темизации. Компонент «Кнопка» определяет логику (нажатие, состояние загрузки), но делегирует внешний вид текущей Теме.

Вы можете подменить тему (Implementation) со светлой на темную, не меняя код кнопки (Abstraction).

Facade

Фасад — простой интерфейс для сложной системы. Он скрывает за собой «магию» инициализации, зависимостей и сложной логики.

Скрытие сложности

    graph LR
        UI["Frontend Component"] --> Facade["API Service (Facade)"]
        Facade --> Auth["Auth Module"]
        Facade --> Http["Axios Config"]
        Facade --> Cache["LocalStorage"]
        Facade --> Error["Error Handler"]
    

API Service

Когда вы в компоненте вызываете метод buyProduct(id), вы используете Фасад. Вам не нужно знать, что внутри этой функции:

  • Проверяется токен авторизации.
  • Данные сериализуются в JSON.
  • Делается запрос через Axios.
  • Если ошибка 401 — делается refresh токена и повтор запроса.
  • Результат кэшируется.

Для компонента это просто одна строка кода. Вся сложность скрыта в файле api.js или кастомном хуке useCheckout.


// Facade хук или сервис
class ApiFacade {
    static async getUserProfile() {
        // Скрытая сложная логика
        const token = localStorage.getItem('token');
        if (!token) throw new Error("No token");

        const response = await fetch('/api/me', {
            headers: { Authorization: `Bearer ${token}` }
        });
        return response.json();
    }
}

// Клиентский код (просто и чисто)
const profile = await ApiFacade.getUserProfile();
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ