- Home » 
 
      
								Шаблон проектирования 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 — этот паттерн поможет создать элегантное и поддерживаемое решение. Главное — не переусложнять там, где можно обойтись простыми решениями, но и не бояться применять его там, где он действительно оправдан.
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.