Decorator (Декоратор)

Проблема

Работая с тестовыми данными, команда сталкивается с несколькими сквозными потребностями: логировать все операции с данными (для дебага упавшего теста), добавлять retry при нестабильном внешнем хранилище, собирать метрики. Эти аспекты не связаны с основной логикой репозитория, но их приходится вставлять прямо в основной класс — он разрастается и нарушает принцип единственной ответственности.

Decorator позволяет навешивать дополнительное поведение на объект, не изменяя его класс. Декоратор реализует тот же интерфейс, что и декорируемый объект, принимает его в конструктор и расширяет поведение. Слои можно комбинировать в любом порядке: Logging(Retry(InMemory)).

Структура модуля

decorator/
├── build.gradle
└── src/
    ├── main/
    │   └── java/
    │       └── dev/shipovalov/decorator/
    │           ├── UserTestRepository.java          # Интерфейс репозитория тестовых пользователей
    │           ├── InMemoryUserTestRepository.java  # Базовая реализация: хранение в Map
    │           ├── LoggingUserTestRepository.java   # Декоратор: логирует каждую операцию
    │           ├── RetryUserTestRepository.java     # Декоратор: повторяет операцию при исключении
    │           └── User.java                        # Модель пользователя
    └── test/
        └── java/
            └── dev/shipovalov/decorator/
                └── DecoratorPatternTest.java        # Тест с различными комбинациями декораторов

Ключевые классы

Класс Назначение
UserTestRepository Интерфейс: create(String name, String email), findById(long), delete(long)
InMemoryUserTestRepository Чистая реализация без побочных эффектов
LoggingUserTestRepository Оборачивает любой репозиторий, логирует до и после каждого вызова
RetryUserTestRepository Перехватывает исключения и повторяет вызов N раз с задержкой

Пример использования

// Только базовая реализация
UserTestRepository repo = new InMemoryUserTestRepository();

// + логирование
UserTestRepository withLogging = new LoggingUserTestRepository(repo);

// + retry поверх логирования (retry срабатывает, логирование видит каждую попытку)
UserTestRepository withRetryAndLogging = new RetryUserTestRepository(withLogging, 3);

// + retry поверх, логирование только успешных (другой порядок — другое поведение)
UserTestRepository loggingRetry = new LoggingUserTestRepository(
    new RetryUserTestRepository(repo, 3)
);

User user = withRetryAndLogging.create("Иван", "ivan@test.com");
// user — Record: user.id(), user.name(), user.email()

Запуск тестов

./gradlew :decorator:test

Отчёт о результатах: decorator/build/reports/tests/test/index.html