Чтобы проиллюстрировать, как вы можете Шаблон цепочки ответственности Я собрал небольшой пример.Я полагаю, что вы сможете адаптировать это решение в соответствии с потребностями вашей реальной проблемы.
Пространство проблемы
У нас есть неизвестный набор пользовательских запросов, которые содержатИмя свойств, которые будут получены.Существует несколько источников данных, каждый из которых имеет различное количество свойств.Мы хотим выполнить поиск во всех возможных источниках данных, пока все свойства из запроса не будут обнаружены.Некоторые типы данных и источники данных могут выглядеть следующим образом: (обратите внимание, я для краткости использую Lombok ) :
@lombok.Data
class FooBarData {
private final String foo;
private final String bar;
}
@lombok.Data
class FizzBuzzData {
private final String fizz;
private final String buzz;
}
class FooBarService {
public FooBarData invoke() {
System.out.println("This is an expensive FooBar call");
return new FooBarData("FOO", "BAR");
}
}
class FizzBuzzService {
public FizzBuzzData invoke() {
System.out.println("This is an expensive FizzBuzz call");
return new FizzBuzzData("FIZZ", "BUZZ");
}
}
Для нашего конечного пользователя может потребоваться несколько способов разрешенияданные.Следующее может быть допустимым пользовательским вводом и ожидаемым ответом:
// Input
"foobar", "foo", "fizz"
// Output
{
"foobar" : {
"foo" : "FOO",
"bar" : "BAR"
},
"foo" : "FOO",
"fizz" : "FIZZ"
}
Базовый интерфейс и простая конкретная реализация для нашего преобразователя свойств могут выглядеть следующим образом:
interface PropertyResolver {
Map<String, Object> resolve(List<String> properties);
}
class UnknownResolver implements PropertyResolver {
@Override
public Map<String, Object> resolve(List<String> properties) {
Map<String, Object> result = new HashMap<>();
for (String property : properties) {
result.put(property, "Unknown");
}
return result;
}
}
Область решения
Вместо использования обычного «шаблона декоратора» лучшим решением может быть «шаблон цепи ответственности» .Этот шаблон аналогичен шаблону декоратора, однако каждому звену в цепочке разрешается либо работать с элементом, игнорировать элемент или завершать выполнение.Это полезно для принятия решения о необходимости вызова или завершения цепочки, если работа по запросу завершена.Другое отличие от шаблона декоратора состоит в том, что resolve
не будет переопределено каждым из конкретных классов;наш абстрактный класс может вызывать подкласс, когда это необходимо, используя абстрактные методы.
Возвращаясь к рассматриваемой проблеме ... Для каждого распознавателя нам нужны два компонента.Способ извлечения данных из нашего удаленного сервиса и способ извлечения всех необходимых свойств из полученных данных.Для извлечения данных мы можем предоставить абстрактный метод.Для извлечения свойства из извлеченных данных мы можем создать небольшой интерфейс и вести список этих экстракторов, видя, как из одного куска данных можно извлечь несколько свойств:
interface PropertyExtractor<Data> {
Object extract(Data data);
}
abstract class PropertyResolverChain<Data> implements PropertyResolver {
private final Map<String, PropertyExtractor<Data>> extractors = new HashMap<>();
private final PropertyResolver successor;
protected PropertyResolverChain(PropertyResolver successor) {
this.successor = successor;
}
protected abstract Data getData();
protected final void setBinding(String property, PropertyExtractor<Data> extractor) {
extractors.put(property, extractor);
}
@Override
public Map<String, Object> resolve(List<String> properties) {
...
}
}
Основная идея *Метод 1039 * - сначала определить, какой properties
может быть выполнен этим экземпляром PropertyResolver
.Если есть подходящие свойства, тогда мы получим данные, используя getData
.Для каждого подходящего свойства мы извлекаем значение свойства и добавляем его в карту результатов.Для каждого свойства, которое не может быть разрешено, successor
будет запрошено разрешение этого свойства.Если все свойства разрешены, цепочка выполнения завершится.
@Override
public Map<String, Object> resolve(List<String> properties) {
Map<String, Object> result = new HashMap<>();
List<String> eligibleProperties = new ArrayList<>(properties);
eligibleProperties.retainAll(extractors.keySet());
if (!eligibleProperties.isEmpty()) {
Data data = getData();
for (String property : eligibleProperties) {
result.put(property, extractors.get(property).extract(data));
}
}
List<String> remainingProperties = new ArrayList<>(properties);
remainingProperties.removeAll(eligibleProperties);
if (!remainingProperties.isEmpty()) {
result.putAll(successor.resolve(remainingProperties));
}
return result;
}
Реализация резолверов
Когда мы перейдем к реализации конкретного класса для PropertyResolverChain
, нам нужно будет реализовать getData
метод, а также связать PropertyExtractor
экземпляров.Эти привязки могут действовать как адаптер для данных, возвращаемых каждой службой.Эти данные могут иметь ту же структуру, что и данные, возвращаемые службой, или иметь собственную схему.Используя FooBarService
из более раннего примера, наш класс может быть реализован следующим образом: (обратите внимание, что у нас может быть много привязок, в результате которых возвращаются одни и те же данные) .
class FooBarResolver extends PropertyResolverChain<FooBarData> {
private final FooBarService remoteService;
FooBarResolver(PropertyResolver successor, FooBarService remoteService) {
super(successor);
this.remoteService = remoteService;
// return the whole object
setBinding("foobar", data -> data);
// accept different spellings
setBinding("foo", data -> data.getFoo());
setBinding("bar", data -> data.getBar());
setBinding("FOO", data -> data.getFoo());
setBinding("__bar", data -> data.getBar());
// create new properties all together!!
setBinding("barfoo", data -> data.getBar() + data.getFoo());
}
@Override
protected FooBarData getData() {
return remoteService.invoke();
}
}
Пример использования
Собрав все вместе, мы можем вызвать цепочку Resolver
, как показано ниже.Мы можем заметить, что дорогой вызов метода getData
выполняется только один раз за Resolver
, только если свойство связано с распознавателем, и что пользователь получает только те поля, которые ему требуются:
PropertyResolver resolver =
new FizzBuzzResolver(
new FooBarResolver(
new UnknownResolver(),
new FooBarService()),
new FizzBuzzService());
Map<String, Object> result = resolver.resolve(Arrays.asList(
"foobar", "foo", "__bar", "barfoo", "invalid", "fizz"));
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
System.out.println(mapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(result));
Вывод
This is an expensive FizzBuzz call
This is an expensive FooBar call
{
"foobar" : {
"foo" : "FOO",
"bar" : "BAR"
},
"__bar" : "BAR",
"barfoo" : "BARFOO",
"foo" : "FOO",
"invalid" : "Unknown",
"fizz" : "FIZZ"
}