【问题标题】:Using decorator pattern without adding "different" behaviour使用装饰器模式而不添加“不同”行为
【发布时间】:2018-11-14 12:26:39
【问题描述】:

我有外观界面,用户可以在其中询问有关工程师的信息。该信息应作为我们为其创建 DTO 的 JSON 传输。现在请记住,我有多个数据源可以为这个 DTO 列表提供一个项目。

所以我现在相信我可以通过将数据源的处理程序添加到List<EngineerDTO> 类型的myEngineerListDTO 来使用装饰模式。因此,我的意思是所有数据源都具有相同的 DTO。

下图显示 VerticalScrollbar 和 Horizo​​ntalScrollBar 添加了不同的行为。这意味着它们将行为添加到 WindowDecorator 界面。

我的问题,我的情况符合装饰者模式吗?我是否特别需要添加一个行为来使用这种模式?还有另一种适合我情况的模式吗?我已经考虑过责任链模式,但是因为我不需要在任何给定时刻终止我的链,我认为装饰器模式可能会更好。

编辑: 我的最终结果应该是:来自所有数据源的List<EngineersDTO>。我想添加此模式的原因是我可以轻松地在“管道”的其余部分后面添加另一个数据源。与其他数据源一样,此数据源将具有 addEngineersDTOToList 方法。

【问题讨论】:

  • 在我看来,您的用例适合装饰器模式(添加不同的行为,在您的情况下添加不同的滚动条)。这是另一个如何使用装饰器模式的示例:oodesign.com/decorator-pattern-gui-example-java-sourcecode.html,也许它会有所帮助
  • @ItFreak 我不认为他们的问题是关于 GUI 的。 OP 正在尝试从多个来源创建 POJO。
  • 那么你想做什么?您正在尝试实现一个 API,它是多个其他远程服务的数据聚合?用户如何控制在 DTO 中提供哪些数据组合,或者它们是固定的 API?
  • @flakes 我不确定,UML 显示了一个 GUI 实现
  • @ItFreak 这也是我第一次阅读时的想法。这个问题可能应该重构一下。

标签: java design-patterns decorator


【解决方案1】:

为了进一步说明如何Chain-of-responsibility pattern 我整理了一个小例子。我相信你应该能够调整这个解决方案来满足你现实世界问题的需要。


问题空间

我们有一组未知的用户请求,其中包含要检索的属性名称。有多个数据源,每个数据源都有不同数量的属性。我们希望搜索所有可能的数据源,直到发现请求中的所有属性。某些数据类型和数据源可能如下所示(注意我使用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) {
        ...
    }
}

resolve 方法的基本思想是首先评估这个PropertyResolver 实例可以满足哪个properties。如果有符合条件的属性,那么我们将使用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 链,如下所示。我们可以观察到,仅当属性绑定到解析器时,每个 Resolver 只执行一次昂贵的 getData 方法调用,并且用户只能获得他们需要的确切字段:

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"
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-09-19
    • 1970-01-01
    • 1970-01-01
    • 2014-12-29
    • 2013-05-07
    • 1970-01-01
    • 2022-10-20
    • 2015-08-25
    相关资源
    最近更新 更多