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