Некоторое время назад я реорганизовал кодовую базу в использование шаблона Decorator, поэтому я попытаюсь объяснить пример использования.
Предположим, у нас есть набор служб, и в зависимости от того, приобрел ли пользователь лицензию на конкретную услугу, нам нужно запустить службу.
Все сервисы имеют общий интерфейс
interface Service {
String serviceId();
void init() throws Exception;
void start() throws Exception;
void stop() throws Exception;
}
Предварительный рефакторинг
abstract class ServiceSupport implements Service {
public ServiceSupport(String serviceId, LicenseManager licenseManager) {
// assign instance variables
}
@Override
public void init() throws Exception {
if (!licenseManager.isLicenseValid(serviceId)) {
throw new Exception("License not valid for service");
}
// Service initialization logic
}
}
Если вы внимательно наблюдаете, ServiceSupport
зависит от LicenseManager
. Но почему это должно зависеть от LicenseManager
? Что делать, если нам нужен фоновый сервис, который не должен проверять информацию о лицензии. В сложившейся ситуации нам придется как-то обучить LicenseManager
возвращать true
для фоновых услуг.
Этот подход мне не показался удачным. По моему мнению, проверка лицензии и другая логика были ортогональны друг другу.
Итак Pattern Decorator приходит на помощь и здесь начинает рефакторинг с TDD.
Рефакторинг
class LicensedService implements Service {
private Service service;
public LicensedService(LicenseManager licenseManager, Service service) {
this.service = service;
}
@Override
public void init() {
if (!licenseManager.isLicenseValid(service.serviceId())) {
throw new Exception("License is invalid for service " + service.serviceId());
}
// Delegate init to decorated service
service.init();
}
// override other methods according to requirement
}
// Not concerned with licensing any more :)
abstract class ServiceSupport implements Service {
public ServiceSupport(String serviceId) {
// assign variables
}
@Override
public void init() {
// Service initialization logic
}
}
// The services which need license protection can be decorated with a Licensed service
Service aLicensedService = new LicensedService(new Service1("Service1"), licenseManager);
// Services which don't need license can be created without one and there is no need to pass license related information
Service aBackgroundService = new BackgroundService1("BG-1");
Takeaways
- Сплоченность кода стала лучше
- Модульное тестирование стало проще, поскольку при тестировании ServiceSupport не нужно имитировать лицензирование
- Не нужно обходить лицензирование какими-либо специальными проверками для фоновых услуг
- Правильное разделение обязанностей