Lapa Knowledge Base
Поведенческие паттерны

Strategy

Паттерн Strategy

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

Проблема

Представьте, что вы создаете приложение для навигации. В первой версии приложения вы можете строить маршруты только для автомобилей. В следующей версии вы добавляете поддержку пешеходных маршрутов. Позже вы добавляете возможность строить маршруты с использованием общественного транспорта.

Если вы посмотрите на код, то увидите гигантский условный оператор, который переключается между различными алгоритмами построения маршрутов. Хотя такой код работает, он сложен для поддержки и расширения.

Решение

Паттерн Strategy предлагает взять семейство алгоритмов и вынести их в собственные классы, называемые стратегиями. Вместо того чтобы изначальный класс сам выполнял алгоритм, он делегирует работу связанному объекту-стратегии.


Структура

Strategy
└── execute(a: any, b: any): any

ConcreteStrategyA
└── execute(a: any, b: any): any

ConcreteStrategyB
└── execute(a: any, b: any): any

Context
├── strategy: Strategy
├── setStrategy(strategy: Strategy): void
└── executeStrategy(a: any, b: any): any

Пример реализации

JavaScript

// Интерфейс стратегии
class PaymentStrategy {
    pay(amount) {
        throw new Error('Метод pay должен быть переопределен');
    }
}

// Конкретные стратегии
class CreditCardPayment extends PaymentStrategy {
    constructor(cardNumber, cvv, expiryDate) {
        super();
        this.cardNumber = cardNumber;
        this.cvv = cvv;
        this.expiryDate = expiryDate;
    }
    
    pay(amount) {
        console.log(`Оплата ${amount} руб. кредитной картой ${this.cardNumber}`);
        console.log(`CVV: ${this.cvv}, срок действия: ${this.expiryDate}`);
        return { success: true, method: 'credit_card', amount: amount };
    }
}

class PayPalPayment extends PaymentStrategy {
    constructor(email) {
        super();
        this.email = email;
    }
    
    pay(amount) {
        console.log(`Оплата ${amount} руб. через PayPal (${this.email})`);
        return { success: true, method: 'paypal', amount: amount };
    }
}

class BankTransferPayment extends PaymentStrategy {
    constructor(accountNumber, bankCode) {
        super();
        this.accountNumber = accountNumber;
        this.bankCode = bankCode;
    }
    
    pay(amount) {
        console.log(`Оплата ${amount} руб. банковским переводом`);
        console.log(`Счет: ${this.accountNumber}, банк: ${this.bankCode}`);
        return { success: true, method: 'bank_transfer', amount: amount };
    }
}

class CryptocurrencyPayment extends PaymentStrategy {
    constructor(walletAddress, currency) {
        super();
        this.walletAddress = walletAddress;
        this.currency = currency;
    }
    
    pay(amount) {
        console.log(`Оплата ${amount} руб. криптовалютой ${this.currency}`);
        console.log(`Кошелек: ${this.walletAddress}`);
        return { success: true, method: 'cryptocurrency', amount: amount };
    }
}

// Контекст - платежная система
class PaymentProcessor {
    constructor() {
        this.strategy = null;
    }
    
    setPaymentStrategy(strategy) {
        this.strategy = strategy;
    }
    
    processPayment(amount) {
        if (!this.strategy) {
            throw new Error('Стратегия оплаты не выбрана');
        }
        
        console.log('=== Обработка платежа ===');
        const result = this.strategy.pay(amount);
        console.log('Платеж обработан успешно!');
        return result;
    }
}

// Использование
const paymentProcessor = new PaymentProcessor();

// Разные способы оплаты
const creditCard = new CreditCardPayment('1234-5678-9012-3456', '123', '12/25');
const paypal = new PayPalPayment('user@example.com');
const bankTransfer = new BankTransferPayment('1234567890', 'SBER');
const crypto = new CryptocurrencyPayment('1A2B3C4D5E6F7G8H9I0J', 'BTC');

// Обрабатываем платежи разными способами
console.log('--- Оплата кредитной картой ---');
paymentProcessor.setPaymentStrategy(creditCard);
paymentProcessor.processPayment(1000);

console.log('\n--- Оплата через PayPal ---');
paymentProcessor.setPaymentStrategy(paypal);
paymentProcessor.processPayment(500);

console.log('\n--- Банковский перевод ---');
paymentProcessor.setPaymentStrategy(bankTransfer);
paymentProcessor.processPayment(2000);

console.log('\n--- Криптовалюта ---');
paymentProcessor.setPaymentStrategy(crypto);
paymentProcessor.processPayment(1500);

TypeScript с интерфейсами

// Интерфейс стратегии сортировки
interface SortStrategy {
    sort(data: number[]): number[];
}

// Конкретные стратегии сортировки
class BubbleSortStrategy implements SortStrategy {
    sort(data: number[]): number[] {
        console.log('Сортировка пузырьком');
        const arr = [...data];
        const n = arr.length;
        
        for (let i = 0; i < n - 1; i++) {
            for (let j = 0; j < n - i - 1; j++) {
                if (arr[j] > arr[j + 1]) {
                    [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
                }
            }
        }
        
        return arr;
    }
}

class QuickSortStrategy implements SortStrategy {
    sort(data: number[]): number[] {
        console.log('Быстрая сортировка');
        return this.quickSort([...data], 0, data.length - 1);
    }
    
    private quickSort(arr: number[], low: number, high: number): number[] {
        if (low < high) {
            const pivotIndex = this.partition(arr, low, high);
            this.quickSort(arr, low, pivotIndex - 1);
            this.quickSort(arr, pivotIndex + 1, high);
        }
        return arr;
    }
    
    private partition(arr: number[], low: number, high: number): number {
        const pivot = arr[high];
        let i = low - 1;
        
        for (let j = low; j < high; j++) {
            if (arr[j] <= pivot) {
                i++;
                [arr[i], arr[j]] = [arr[j], arr[i]];
            }
        }
        
        [arr[i + 1], arr[high]] = [arr[high], arr[i + 1]];
        return i + 1;
    }
}

class MergeSortStrategy implements SortStrategy {
    sort(data: number[]): number[] {
        console.log('Сортировка слиянием');
        return this.mergeSort([...data]);
    }
    
    private mergeSort(arr: number[]): number[] {
        if (arr.length <= 1) {
            return arr;
        }
        
        const mid = Math.floor(arr.length / 2);
        const left = this.mergeSort(arr.slice(0, mid));
        const right = this.mergeSort(arr.slice(mid));
        
        return this.merge(left, right);
    }
    
    private merge(left: number[], right: number[]): number[] {
        const result: number[] = [];
        let i = 0, j = 0;
        
        while (i < left.length && j < right.length) {
            if (left[i] <= right[j]) {
                result.push(left[i]);
                i++;
            } else {
                result.push(right[j]);
                j++;
            }
        }
        
        return result.concat(left.slice(i)).concat(right.slice(j));
    }
}

// Контекст - сортировщик
class Sorter {
    private strategy: SortStrategy;
    
    constructor(strategy: SortStrategy) {
        this.strategy = strategy;
    }
    
    setStrategy(strategy: SortStrategy): void {
        this.strategy = strategy;
    }
    
    sort(data: number[]): number[] {
        console.log(`Исходный массив: [${data.join(', ')}]`);
        const startTime = performance.now();
        
        const sorted = this.strategy.sort(data);
        
        const endTime = performance.now();
        console.log(`Отсортированный массив: [${sorted.join(', ')}]`);
        console.log(`Время выполнения: ${(endTime - startTime).toFixed(2)} мс`);
        
        return sorted;
    }
}

// Использование
const data = [64, 34, 25, 12, 22, 11, 90, 5, 77, 30];
const sorter = new Sorter(new BubbleSortStrategy());

console.log('=== Тестирование различных стратегий сортировки ===');

console.log('\n--- Пузырьковая сортировка ---');
sorter.sort(data);

console.log('\n--- Быстрая сортировка ---');
sorter.setStrategy(new QuickSortStrategy());
sorter.sort(data);

console.log('\n--- Сортировка слиянием ---');
sorter.setStrategy(new MergeSortStrategy());
sorter.sort(data);

Когда использовать

Используйте Strategy, когда:

  • Есть несколько способов выполнения задачи
  • Нужно выбирать алгоритм во время выполнения
  • Хотите избежать условных операторов для выбора алгоритма
  • Нужно изолировать детали реализации алгоритмов от клиентского кода

Преимущества

  • Алгоритмы можно переключать во время выполнения
  • Изолирует детали реализации алгоритмов
  • Принцип открытости/закрытости: можно добавлять новые стратегии
  • Устраняет условные операторы для выбора алгоритма

Недостатки

  • Клиенты должны знать о различных стратегиях
  • Увеличивает количество классов в приложении
  • Может усложнить код, если стратегии отличаются только параметрами

Отличия от других паттернов

  • State — изменяет поведение объекта при изменении состояния
  • Command — инкапсулирует запросы как объекты
  • Template Method — определяет скелет алгоритма

Реальный пример

// Strategy для валидации форм
class ValidationStrategy {
    validate(value) {
        throw new Error('Метод validate должен быть переопределен');
    }
}

class EmailValidation extends ValidationStrategy {
    validate(email) {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        return emailRegex.test(email);
    }
}

class PhoneValidation extends ValidationStrategy {
    validate(phone) {
        const phoneRegex = /^\+?[\d\s\-\(\)]{10,}$/;
        return phoneRegex.test(phone);
    }
}

class PasswordValidation extends ValidationStrategy {
    validate(password) {
        return password.length >= 8 && 
               /[A-Z]/.test(password) && 
               /[a-z]/.test(password) && 
               /\d/.test(password);
    }
}

class FormValidator {
    constructor() {
        this.strategies = new Map();
    }
    
    addValidation(fieldName, strategy) {
        this.strategies.set(fieldName, strategy);
    }
    
    validateForm(formData) {
        const errors = {};
        
        for (const [fieldName, strategy] of this.strategies) {
            const value = formData[fieldName];
            if (!strategy.validate(value)) {
                errors[fieldName] = `Поле ${fieldName} не прошло валидацию`;
            }
        }
        
        return {
            isValid: Object.keys(errors).length === 0,
            errors: errors
        };
    }
}

// Использование
const validator = new FormValidator();

validator.addValidation('email', new EmailValidation());
validator.addValidation('phone', new PhoneValidation());
validator.addValidation('password', new PasswordValidation());

const formData = {
    email: 'user@example.com',
    phone: '+7 (999) 123-45-67',
    password: 'MyPassword123'
};

const result = validator.validateForm(formData);
console.log('Результат валидации:', result);

Strategy — это гибкость в выборе алгоритмов!

Copyright © 2026