Home » Шаблон проектирования Strategy в Java — пример и руководство
Шаблон проектирования Strategy в Java — пример и руководство

Шаблон проектирования Strategy в Java — пример и руководство

Сталкивался ли ты с ситуацией, когда нужно было реализовать несколько алгоритмов для одной задачи, но каждый раз приходилось переписывать кучу условий if-else? Или когда добавление нового алгоритма превращалось в настоящий кошмар из-за жёстко связанного кода? Паттерн Strategy — твоё спасение! Это один из самых практичных и элегантных способов решения таких проблем, особенно когда речь идёт о серверных приложениях, где гибкость и расширяемость критически важны.

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

Как работает паттерн Strategy

Strategy (Стратегия) — это поведенческий паттерн проектирования, который позволяет выбирать алгоритм во время выполнения программы. Основная идея проста: вместо того чтобы реализовывать алгоритм напрямую, контекст получает ссылку на один из объектов стратегии и делегирует ему выполнение работы.

Структура паттерна включает три основных компонента:

  • Strategy (Стратегия) — интерфейс, общий для всех конкретных стратегий
  • ConcreteStrategy (Конкретная стратегия) — реализация алгоритма с использованием интерфейса стратегии
  • Context (Контекст) — содержит ссылку на объект стратегии и позволяет стратегии получить доступ к своим данным

Пошаговая реализация Strategy в Java

Давай создадим практический пример системы мониторинга CPU на сервере с разными стратегиями анализа нагрузки. Это типичная задача для любого сисадмина!

Шаг 1: Создаём интерфейс стратегии

public interface CpuAnalysisStrategy {
    String analyzeCpuUsage(double cpuUsage);
    String getStrategyName();
}

Шаг 2: Реализуем конкретные стратегии

// Консервативная стратегия для production серверов
public class ConservativeAnalysisStrategy implements CpuAnalysisStrategy {
    @Override
    public String analyzeCpuUsage(double cpuUsage) {
        if (cpuUsage > 70) {
            return "CRITICAL: CPU usage too high! Consider scaling.";
        } else if (cpuUsage > 50) {
            return "WARNING: CPU usage is elevated. Monitor closely.";
        } else {
            return "OK: CPU usage is normal.";
        }
    }
    
    @Override
    public String getStrategyName() {
        return "Conservative Analysis";
    }
}

// Агрессивная стратегия для тестовых серверов
public class AggressiveAnalysisStrategy implements CpuAnalysisStrategy {
    @Override
    public String analyzeCpuUsage(double cpuUsage) {
        if (cpuUsage > 90) {
            return "CRITICAL: CPU usage extremely high!";
        } else if (cpuUsage > 80) {
            return "WARNING: CPU usage high but acceptable for testing.";
        } else {
            return "OK: CPU usage is fine.";
        }
    }
    
    @Override
    public String getStrategyName() {
        return "Aggressive Analysis";
    }
}

// Динамическая стратегия с учётом времени суток
public class DynamicAnalysisStrategy implements CpuAnalysisStrategy {
    @Override
    public String analyzeCpuUsage(double cpuUsage) {
        int hour = java.time.LocalTime.now().getHour();
        boolean isBusinessHours = hour >= 9 && hour <= 17;
        
        double threshold = isBusinessHours ? 60 : 80;
        
        if (cpuUsage > threshold) {
            return String.format("WARNING: CPU usage %.1f%% exceeds %s threshold (%.1f%%)", 
                               cpuUsage, isBusinessHours ? "business hours" : "off-hours", threshold);
        } else {
            return String.format("OK: CPU usage %.1f%% is within acceptable range", cpuUsage);
        }
    }
    
    @Override
    public String getStrategyName() {
        return "Dynamic Time-Based Analysis";
    }
}

Шаг 3: Создаём контекст

public class ServerMonitor {
    private CpuAnalysisStrategy strategy;
    
    public ServerMonitor(CpuAnalysisStrategy strategy) {
        this.strategy = strategy;
    }
    
    public void setStrategy(CpuAnalysisStrategy strategy) {
        this.strategy = strategy;
    }
    
    public void performAnalysis(double cpuUsage) {
        System.out.println("Using strategy: " + strategy.getStrategyName());
        System.out.println("Analysis result: " + strategy.analyzeCpuUsage(cpuUsage));
        System.out.println("---");
    }
}

Шаг 4: Тестируем наше решение

public class StrategyPatternDemo {
    public static void main(String[] args) {
        // Имитируем разные уровни нагрузки CPU
        double[] cpuUsages = {45.0, 65.0, 85.0, 95.0};
        
        // Создаём монитор с консервативной стратегией
        ServerMonitor monitor = new ServerMonitor(new ConservativeAnalysisStrategy());
        
        System.out.println("=== Production Server Monitoring ===");
        for (double usage : cpuUsages) {
            System.out.println("CPU Usage: " + usage + "%");
            monitor.performAnalysis(usage);
        }
        
        // Переключаемся на агрессивную стратегию
        monitor.setStrategy(new AggressiveAnalysisStrategy());
        
        System.out.println("=== Test Server Monitoring ===");
        for (double usage : cpuUsages) {
            System.out.println("CPU Usage: " + usage + "%");
            monitor.performAnalysis(usage);
        }
        
        // Используем динамическую стратегию
        monitor.setStrategy(new DynamicAnalysisStrategy());
        
        System.out.println("=== Dynamic Monitoring ===");
        for (double usage : cpuUsages) {
            System.out.println("CPU Usage: " + usage + "%");
            monitor.performAnalysis(usage);
        }
    }
}

Реальные кейсы использования Strategy

Сценарий Описание Преимущества Недостатки
Стратегии кеширования LRU, LFU, FIFO алгоритмы для разных типов данных Легко переключаться между алгоритмами Дополнительная сложность кода
Алгоритмы балансировки Round Robin, Weighted, Least Connections Адаптация под нагрузку в реальном времени Больше классов для поддержки
Методы аутентификации JWT, OAuth, Basic Auth, API Keys Поддержка множественных методов Усложнение конфигурации
Стратегии логирования File, Database, Syslog, Remote logging Гибкая настройка вывода логов Требует хорошего планирования

Продвинутый пример: система backup с разными стратегиями

Вот более сложный пример, который пригодится для автоматизации backup-процессов на сервере:

// Интерфейс стратегии backup
public interface BackupStrategy {
    boolean performBackup(String sourcePath, String destinationPath);
    String getCompressionType();
    long getEstimatedTime(long fileSize);
}

// Быстрая стратегия без сжатия
public class FastBackupStrategy implements BackupStrategy {
    @Override
    public boolean performBackup(String sourcePath, String destinationPath) {
        System.out.println("Starting fast backup (no compression)...");
        // Имитация копирования файлов
        try {
            Thread.sleep(1000); // Имитация времени выполнения
            System.out.println("Fast backup completed successfully!");
            return true;
        } catch (InterruptedException e) {
            return false;
        }
    }
    
    @Override
    public String getCompressionType() {
        return "none";
    }
    
    @Override
    public long getEstimatedTime(long fileSize) {
        return fileSize / 1000000; // Примерно 1MB/ms
    }
}

// Сжатая стратегия с gzip
public class CompressedBackupStrategy implements BackupStrategy {
    @Override
    public boolean performBackup(String sourcePath, String destinationPath) {
        System.out.println("Starting compressed backup with gzip...");
        try {
            Thread.sleep(3000); // Больше времени из-за сжатия
            System.out.println("Compressed backup completed successfully!");
            return true;
        } catch (InterruptedException e) {
            return false;
        }
    }
    
    @Override
    public String getCompressionType() {
        return "gzip";
    }
    
    @Override
    public long getEstimatedTime(long fileSize) {
        return fileSize / 300000; // Медленнее из-за сжатия
    }
}

// Зашифрованная стратегия
public class EncryptedBackupStrategy implements BackupStrategy {
    @Override
    public boolean performBackup(String sourcePath, String destinationPath) {
        System.out.println("Starting encrypted backup with AES-256...");
        try {
            Thread.sleep(5000); // Ещё больше времени из-за шифрования
            System.out.println("Encrypted backup completed successfully!");
            return true;
        } catch (InterruptedException e) {
            return false;
        }
    }
    
    @Override
    public String getCompressionType() {
        return "aes256";
    }
    
    @Override
    public long getEstimatedTime(long fileSize) {
        return fileSize / 200000; // Самый медленный
    }
}

// Контекст для управления backup
public class BackupManager {
    private BackupStrategy strategy;
    
    public BackupManager(BackupStrategy strategy) {
        this.strategy = strategy;
    }
    
    public void setStrategy(BackupStrategy strategy) {
        this.strategy = strategy;
    }
    
    public boolean executeBackup(String source, String destination, long fileSize) {
        long estimatedTime = strategy.getEstimatedTime(fileSize);
        
        System.out.println("Backup Configuration:");
        System.out.println("- Compression: " + strategy.getCompressionType());
        System.out.println("- Estimated time: " + estimatedTime + " ms");
        System.out.println("- Source: " + source);
        System.out.println("- Destination: " + destination);
        System.out.println();
        
        return strategy.performBackup(source, destination);
    }
}

Автоматизация и скрипты с Strategy

Strategy особенно мощен в контексте автоматизации. Ты можешь создать скрипт, который выбирает стратегию на основе условий сервера:

public class AutomatedBackupScript {
    public static void main(String[] args) {
        BackupManager backupManager = new BackupManager(new FastBackupStrategy());
        
        // Проверяем доступные ресурсы системы
        long availableMemory = Runtime.getRuntime().freeMemory();
        long totalSpace = new File("/").getTotalSpace();
        int currentHour = java.time.LocalTime.now().getHour();
        
        // Выбираем стратегию в зависимости от условий
        BackupStrategy chosenStrategy = chooseOptimalStrategy(
            availableMemory, totalSpace, currentHour
        );
        
        backupManager.setStrategy(chosenStrategy);
        
        // Выполняем backup
        String source = "/var/www/html";
        String destination = "/backup/website_" + java.time.LocalDate.now();
        long estimatedFileSize = 1000000000L; // 1GB
        
        boolean success = backupManager.executeBackup(source, destination, estimatedFileSize);
        
        if (success) {
            System.out.println("Automated backup completed successfully!");
        } else {
            System.err.println("Backup failed!");
        }
    }
    
    private static BackupStrategy chooseOptimalStrategy(
        long availableMemory, long totalSpace, int currentHour
    ) {
        // Ночные часы - можно использовать медленную но эффективную стратегию
        if (currentHour >= 22 || currentHour <= 6) {
            return new EncryptedBackupStrategy();
        }
        
        // Мало места - используем сжатие
        if (totalSpace < 10000000000L) { // Меньше 10GB
            return new CompressedBackupStrategy();
        }
        
        // Мало памяти - быстрая стратегия
        if (availableMemory < 500000000L) { // Меньше 500MB
            return new FastBackupStrategy();
        }
        
        // По умолчанию - сжатие
        return new CompressedBackupStrategy();
    }
}

Интеграция с другими технологиями

Strategy отлично работает в связке с другими паттернами и технологиями:

  • Spring Framework — можно использовать dependency injection для автоматического выбора стратегии
  • Factory Pattern — создание стратегий через фабрику на основе конфигурации
  • Observer Pattern — уведомления о смене стратегии
  • Configuration Management — выбор стратегии из конфигурационных файлов

Сравнение с альтернативными решениями

Подход Гибкость Производительность Сложность Тестируемость
If-else цепочки Низкая Высокая Низкая Средняя
Switch-case Низкая Высокая Низкая Средняя
Strategy Pattern Высокая Средняя Средняя Высокая
Command Pattern Высокая Средняя Высокая Высокая

Полезные ресурсы и инструменты

Для более глубокого изучения паттернов проектирования рекомендую:

Если ты планируешь разворачивать свои Java-приложения на продакшене, обязательно позаботься о качественной серверной инфраструктуре. Для тестирования и разработки отлично подойдёт VPS, а для high-load приложений лучше взять выделенный сервер.

Нестандартные способы применения

Strategy может удивить своей универсальностью:

  • Динамическое изменение алгоритмов сжатия в зависимости от типа файлов
  • Адаптивные стратегии безопасности — усиление защиты при обнаружении подозрительной активности
  • Интеллектуальный выбор баз данных — MySQL для транзакций, Redis для кеша, MongoDB для документов
  • Стратегии деплоя — blue-green, canary, rolling updates в зависимости от критичности обновления

Автоматизация и мониторинг

Strategy открывает огромные возможности для автоматизации серверных процессов:

// Пример автоматического выбора стратегии мониторинга
public class AdaptiveMonitoringSystem {
    private Map strategies;
    private ServerMonitor monitor;
    
    public AdaptiveMonitoringSystem() {
        strategies = new HashMap<>();
        strategies.put("production", new ConservativeAnalysisStrategy());
        strategies.put("testing", new AggressiveAnalysisStrategy());
        strategies.put("development", new DynamicAnalysisStrategy());
        
        monitor = new ServerMonitor(strategies.get("production"));
    }
    
    public void adaptToEnvironment(String environment) {
        CpuAnalysisStrategy strategy = strategies.get(environment);
        if (strategy != null) {
            monitor.setStrategy(strategy);
            System.out.println("Switched to " + environment + " monitoring strategy");
        }
    }
    
    public void monitorWithAutoAdaptation(double cpuUsage) {
        // Автоматическое переключение на более чувствительную стратегию
        // при высокой нагрузке
        if (cpuUsage > 90) {
            adaptToEnvironment("production");
        } else if (cpuUsage > 70) {
            adaptToEnvironment("testing");
        } else {
            adaptToEnvironment("development");
        }
        
        monitor.performAnalysis(cpuUsage);
    }
}

Заключение и рекомендации

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

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

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

  • У тебя есть несколько способов решения одной задачи
  • Алгоритм может изменяться в зависимости от условий выполнения
  • Ты хочешь избежать больших if-else или switch-case конструкций
  • Нужна возможность горячей замены алгоритмов без перезапуска приложения

Когда НЕ использовать Strategy:

  • У тебя есть только один или два простых алгоритма
  • Алгоритмы никогда не изменяются
  • Производительность критически важна (Strategy добавляет небольшой overhead)
  • Команда не готова поддерживать более сложную архитектуру

Strategy особенно полезен в серверных приложениях, где требуется адаптивность и гибкость. Будь то система мониторинга, backup-сервис или load balancer — этот паттерн поможет создать элегантное и поддерживаемое решение. Главное — не переусложнять там, где можно обойтись простыми решениями, но и не бояться применять его там, где он действительно оправдан.


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

Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.

Leave a reply

Your email address will not be published. Required fields are marked