← Назад к вопросам

Как организовать работу методов в классе в зависимости от выбранного режима класса

1.8 Middle🔥 161 комментариев
#SOLID и паттерны проектирования

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Как организовать работу методов в зависимости от выбранного режима класса

Проблема

Часто класс должен вести себя по-разному в зависимости от режима (mode). Например:

  • Режим DEBUG vs PRODUCTION
  • Режим READ vs WRITE
  • Режим ONLINE vs OFFLINE

Плохой подход — множественные if/else вычеты внутри каждого метода.

Решение 1: State Pattern (предпочтительно)

Используй паттерн State для инкапсуляции логики каждого режима:

// Интерфейс для каждого режима
public interface FileProcessorMode {
    void processFile(File file);
    void saveFile(File file, String content);
    void deleteFile(File file);
}

// Режим для production
public class ProductionMode implements FileProcessorMode {
    private static final Logger logger = LoggerFactory.getLogger(ProductionMode.class);
    
    @Override
    public void processFile(File file) {
        logger.info("Processing file: " + file.getName());
        // Production логика
    }
    
    @Override
    public void saveFile(File file, String content) {
        logger.info("Saving file: " + file.getName());
        // Обычное сохранение
    }
    
    @Override
    public void deleteFile(File file) {
        logger.info("Deleting file: " + file.getName());
        // Удаление
    }
}

// Режим для debug
public class DebugMode implements FileProcessorMode {
    private static final Logger logger = LoggerFactory.getLogger(DebugMode.class);
    
    @Override
    public void processFile(File file) {
        logger.debug("[DEBUG] Processing file: " + file.getName());
        logger.debug("File size: " + file.length() + " bytes");
        // Debug логика с дополнительной информацией
    }
    
    @Override
    public void saveFile(File file, String content) {
        logger.debug("[DEBUG] Would save file: " + file.getName());
        logger.debug("Content length: " + content.length());
        // Не сохраняем реально в debug
    }
    
    @Override
    public void deleteFile(File file) {
        logger.debug("[DEBUG] Would delete file: " + file.getName());
        // Не удаляем реально в debug
    }
}

// Основной класс
@Service
public class FileProcessor {
    private final FileProcessorMode mode;
    
    // Конструктор инъектирует нужный режим
    public FileProcessor(FileProcessorMode mode) {
        this.mode = mode;
    }
    
    public void process(File file) {
        mode.processFile(file); // Делегируем режиму
    }
    
    public void save(File file, String content) {
        mode.saveFile(file, content); // Делегируем режиму
    }
    
    public void delete(File file) {
        mode.deleteFile(file); // Делегируем режиму
    }
}

// Spring конфигурация
@Configuration
public class ModeConfig {
    @Bean
    @ConditionalOnProperty(name = "app.mode", havingValue = "debug")
    public FileProcessorMode debugMode() {
        return new DebugMode();
    }
    
    @Bean
    @ConditionalOnProperty(name = "app.mode", havingValue = "production")
    public FileProcessorMode productionMode() {
        return new ProductionMode();
    }
}

Решение 2: Strategy Pattern с Enum

public enum ProcessingMode {
    DEBUG(new DebugStrategy()),
    PRODUCTION(new ProductionStrategy()),
    SAFE(new SafeStrategy());
    
    private final Strategy strategy;
    
    ProcessingMode(Strategy strategy) {
        this.strategy = strategy;
    }
    
    public Strategy getStrategy() {
        return strategy;
    }
    
    // Интерфейс Strategy
    public interface Strategy {
        void execute(String data);
        void validate(String data);
    }
    
    // Реализация для DEBUG
    private static class DebugStrategy implements Strategy {
        @Override
        public void execute(String data) {
            System.out.println("[DEBUG] Executing: " + data);
        }
        
        @Override
        public void validate(String data) {
            System.out.println("[DEBUG] Validation disabled");
        }
    }
    
    // Реализация для PRODUCTION
    private static class ProductionStrategy implements Strategy {
        @Override
        public void execute(String data) {
            if (validate(data)) {
                // Выполняем
            }
        }
        
        @Override
        public void validate(String data) {
            if (data == null || data.isEmpty()) {
                throw new IllegalArgumentException("Data cannot be empty");
            }
        }
    }
    
    // Реализация для SAFE
    private static class SafeStrategy implements Strategy {
        @Override
        public void execute(String data) {
            validate(data);
            try {
                // Защищённое выполнение
            } catch (Exception e) {
                // Graceful error handling
            }
        }
        
        @Override
        public void validate(String data) {
            // Строгая валидация
        }
    }
}

// Использование
@Service
public class DataProcessor {
    private final ProcessingMode mode;
    
    public DataProcessor(@Value("${app.processing.mode}") String modeStr) {
        this.mode = ProcessingMode.valueOf(modeStr.toUpperCase());
    }
    
    public void processData(String data) {
        mode.getStrategy().execute(data);
    }
}

Решение 3: Decorator Pattern

Для добавления поведения к существующему функционалу:

// Базовый интерфейс
public interface DataHandler {
    void handle(String data);
}

// Базовая реализация
public class BasicDataHandler implements DataHandler {
    @Override
    public void handle(String data) {
        System.out.println("Handling: " + data);
    }
}

// Декоратор для логирования
public class LoggingDecorator implements DataHandler {
    private final DataHandler handler;
    private final Logger logger;
    
    public LoggingDecorator(DataHandler handler) {
        this.handler = handler;
        this.logger = LoggerFactory.getLogger(LoggingDecorator.class);
    }
    
    @Override
    public void handle(String data) {
        logger.info("Processing data: " + data);
        handler.handle(data);
        logger.info("Processing complete");
    }
}

// Декоратор для валидации
public class ValidationDecorator implements DataHandler {
    private final DataHandler handler;
    
    public ValidationDecorator(DataHandler handler) {
        this.handler = handler;
    }
    
    @Override
    public void handle(String data) {
        if (data == null || data.isEmpty()) {
            throw new IllegalArgumentException("Data is invalid");
        }
        handler.handle(data);
    }
}

// Построение конвеера декораторов
public class DataProcessorConfig {
    @Bean
    public DataHandler dataHandler(
        @Value("${app.debug:false}") boolean debug,
        @Value("${app.validate:true}") boolean validate
    ) {
        DataHandler handler = new BasicDataHandler();
        
        if (debug) {
            handler = new LoggingDecorator(handler);
        }
        
        if (validate) {
            handler = new ValidationDecorator(handler);
        }
        
        return handler;
    }
}

Решение 4: Template Method Pattern

public abstract class DataProcessor {
    
    // Template method — общий алгоритм
    public final void processData(String data) {
        validate(data);
        execute(data);
        log(data);
    }
    
    // Обязательные этапы (должны переопределяться)
    protected abstract void validate(String data);
    protected abstract void execute(String data);
    
    // Опциональный этап (по умолчанию ничего)
    protected void log(String data) {
        System.out.println("Processed: " + data);
    }
}

// Реализация для production
public class ProductionProcessor extends DataProcessor {
    
    @Override
    protected void validate(String data) {
        if (data == null || data.isEmpty()) {
            throw new IllegalArgumentException("Invalid data");
        }
    }
    
    @Override
    protected void execute(String data) {
        System.out.println("Executing in production: " + data);
    }
    
    @Override
    protected void log(String data) {
        System.out.println("[PRODUCTION] Processed: " + data);
    }
}

// Реализация для debug
public class DebugProcessor extends DataProcessor {
    
    @Override
    protected void validate(String data) {
        System.out.println("[DEBUG] Skipping validation");
    }
    
    @Override
    protected void execute(String data) {
        System.out.println("[DEBUG] Would execute: " + data);
    }
    
    @Override
    protected void log(String data) {
        System.out.println("[DEBUG] Processed: " + data + " (length: " + data.length() + ")");
    }
}

Решение 5: Command Pattern

public interface Command {
    void execute();
    void undo();
}

public class FileCommand implements Command {
    private final File file;
    private final FileProcessorMode mode;
    private String originalContent;
    
    public FileCommand(File file, FileProcessorMode mode) {
        this.file = file;
        this.mode = mode;
    }
    
    @Override
    public void execute() {
        mode.processFile(file);
    }
    
    @Override
    public void undo() {
        // Отмена последней операции
    }
}

// Очередь команд
public class CommandInvoker {
    private final Queue<Command> history = new LinkedList<>();
    
    public void executeCommand(Command command) {
        command.execute();
        history.offer(command);
    }
    
    public void undoLastCommand() {
        Command command = history.poll();
        if (command != null) {
            command.undo();
        }
    }
}

Сравнение подходов

ПаттернИспользуй когдаПреимуществаНедостатки
StateМного режимов с разной логикойЧистый код, легко расширятьМного классов
StrategyАлгоритм может быть выбран runtimeГибкостьНужен контекст
DecoratorНужна комбинация поведенийГибкая комбинацияМожет быть сложным
Template MethodОбщий алгоритм с вариациямиПереиспользование кодаЖёсткая иерархия
CommandНужна история операцийОтмена/повторExtra classes

Рекомендуемый подход для 90% случаев

// Enum + Spring @ConditionalOnProperty
public enum AppMode {
    DEBUG,
    PRODUCTION,
    TESTING
}

@Configuration
public class ModeConfig {
    @Bean
    public AppMode appMode(@Value("${app.mode:production}") String mode) {
        return AppMode.valueOf(mode.toUpperCase());
    }
}

@Service
public class MyService {
    private final AppMode mode;
    
    public MyService(AppMode mode) {
        this.mode = mode;
    }
    
    public void doSomething() {
        switch (mode) {
            case DEBUG:
                doDebugVersion();
                break;
            case PRODUCTION:
                doProductionVersion();
                break;
            case TESTING:
                doTestingVersion();
                break;
        }
    }
}

Итог

  1. Для сложных режимов → State Pattern
  2. Для выбора алгоритма → Strategy Pattern
  3. Для добавления функционала → Decorator Pattern
  4. Для пошагового алгоритма → Template Method
  5. Для истории операций → Command Pattern

Выбирай паттерн в зависимости от complexity твоего случая и следуй SOLID принципам!