Использование шаблона декоратора без добавления «другого» поведения - PullRequest
0 голосов
/ 14 ноября 2018

У меня есть интерфейс фасада, где пользователи могут запрашивать информацию, скажем, инженеров.Эта информация должна быть передана в формате JSON, для которого мы создали DTO.Теперь имейте в виду, что у меня есть несколько источников данных, которые могут предоставить элемент в этот список DTO.

Так что сейчас я считаю, что я могу использовать Декоративный шаблон, добавив обработчик источника данных в myEngineerListDTO типа List<EngineerDTO>.Таким образом, я имею в виду, что все источники данных имеют одинаковый DTO.

На этом рисунке ниже показано, что для VerticalScrollbar и HorizontalScrollBar добавлено различное поведение.Что означает, что они добавляют поведение в интерфейс WindowDecorator.
enter image description here

Мой вопрос, соответствует ли моя ситуация шаблону декоратора?Нужно ли мне специально добавлять поведение, чтобы использовать этот шаблон?И есть ли другой шаблон, который подходит для моей ситуации?Я уже рассмотрел шаблон Chain of Responsibility, но поскольку мне не нужно прерывать цепочку в любой момент, я подумал, что, возможно, шаблон Decorator будет лучше.

Редактировать: Мой конечный результат должен быть: List<EngineersDTO> из всех источников данных.Причина, по которой я хочу добавить этот шаблон, заключается в том, что я могу легко добавить другой источник данных за остальную часть «конвейера».Этот источник данных, как и другие, будет иметь метод addEngineersDTOToList.

1 Ответ

0 голосов
/ 14 ноября 2018

Чтобы проиллюстрировать, как вы можете Шаблон цепочки ответственности Я собрал небольшой пример.Я полагаю, что вы сможете адаптировать это решение в соответствии с потребностями вашей реальной проблемы.


Пространство проблемы

У нас есть неизвестный набор пользовательских запросов, которые содержатИмя свойств, которые будут получены.Существует несколько источников данных, каждый из которых имеет различное количество свойств.Мы хотим выполнить поиск во всех возможных источниках данных, пока все свойства из запроса не будут обнаружены.Некоторые типы данных и источники данных могут выглядеть следующим образом: (обратите внимание, я для краткости использую 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"
}
...