两全其美的

使用抽象文档模式的类型安全视图

您如何组织对象? 在本文中,我将介绍一种模式,该模式以无类型的方式在您的系统中组织所谓的名词类,然后使用特征公开数据的类型化视图。 这使得只需少量的牺牲就可以在Java之类的语言中获得JavaScript之类的非类型语言的灵活性。 纳里亚

用户在用户界面中所做的每种配置,表单中的每种选择都需要存储在可从应用程序访问的某个位置。 它需要以一种可以操作的格式存储。 教科书中的示例是为系统中每个名词定义类,并为它们所包含的字段使用getter和setter方法。 做教科书模型的一种更为严肃的方法是为每个名词定义企业bean,并使用注释对其进行处理。 它可能看起来像这样:

siQNe3MEh6TA9QAKcIUf1lA

这些静态模型有局限性。 随着系统的发展,您将需要添加更多字段,更改组件之间的关系,并可能出于不同目的创建其他实现。 你知道这个故事。 突然,每个名词的静态成分不再那么有趣了。 因此,您开始研究其他开发人员。 他们如何解决这个问题? 在JavaScript等非类型化语言中,您可以使用地图来解决此问题。 有关组件的信息可以存储为键值对。 如果一个子系统需要存储一个附加字段,则可以这样做,而无需事先定义该字段。

var myCar = {model: "Tesla", color: "Black"};
myCar.price = 80000; // A new field is defined on-the-fly

它加快了发展速度,但同时付出了巨大的代价。 您会失去类型安全性! 每个真正的Java开发人员的噩梦。 由于您没有使用组件的结构,因此测试和维护也更加困难。 在Speedment进行的最近一次重构中,我们面对静态设计与动态设计的这些问题,并提出了一个称为Abstract Document Pattern的解决方案。

抽象文件模式

swd-IduLEylpsrxRH_ZLVxQ

此模型中的文档类似于JavaScript中的Map。 它包含许多未指定值类型的键值对。 在这个未类型化的抽象文档之上,是许多Traits ,它们表示一个类的特定属性。 特征已使用类型化的方法来检索它们表示的特定值。 名词类只是原始文档接口的抽象基础实现之上的不同特征的并集。 因为一个类可以从多个接口继承,所以可以这样做。

实作

让我们看一下这些组件的来源。

Document.java
public interface Document {
    Object put(String key, Object value);

    Object get(String key);

    <T> Stream<T> children(
            String key,
            Function<Map<String, Object>, T> constructor
    );
}
BaseDocument.java
public abstract class BaseDocument implements Document {

    private final Map<String, Object> entries;

    protected BaseDocument(Map<String, Object> entries) {
        this.entries = requireNonNull(entries);
    }

    @Override
    public final Object put(String key, Object value) {
        return entries.put(key, value);
    }

    @Override
    public final Object get(String key) {
        return entries.get(key);
    }

    @Override
    public final <T> Stream<T> children(
            String key,
            Function<Map<String, Object>, T> constructor) {

        final List<Map<String, Object>> children = 
            (List<Map<String, Object>>) get(key);

        return children == null
                    ? Stream.empty()
                    : children.stream().map(constructor);
    }
}
HasPrice.java
public interface HasPrice extends Document {

    final String PRICE = "price";

    default OptionalInt getPrice() {
        // Use method get() inherited from Document
        final Number num = (Number) get(PRICE); 
        return num == null
            ? OptionalInt.empty()
            : OptionalInt.of(num.intValue());
    }
}

在这里,我们只公开获取价格的吸气剂,但是您当然可以用相同的方法实现一个吸气剂。 这些值始终可以通过put()方法进行修改,但是您面临着将值设置为不同于getter期望的类型的风险。

汽车.java
public final class Car extends BaseDocument
        implements HasColor, HasModel, HasPrice {

    public Car(Map<String, Object> entries) {
        super(entries);
    }

}

如您所见,最终的名词类很少,但是您仍然可以使用类型化的吸气剂访问颜色,型号和价格字段。 向组件添加新值就像将其放入地图一样容易,但是除非它是接口的一部分,否则它不会公开。 该模型还适用于分层组件。 让我们看一下HasWheels特性的外观。

HasWheels.java
public interface HasWheels extends Document {
    final String WHEELS = "wheels";

    Stream<Wheel> getWheels() {
        return children(WHEELS, Wheel::new);
    }
}

就是这么简单! 我们利用以下事实:在Java 8中,您可以将对象的构造函数作为方法引用来引用。 在这种情况下,Wheel类的构造函数仅采用一个参数Map <String,Object>。 这意味着我们可以将其称为Function <Map <String,Object>,Wheel>。

结论

这种模式既有优点,也有缺点。 随着系统的扩展,文档结构易于扩展和构建。 不同的子系统可以通过特征接口公开不同的数据。 根据使用哪个构造函数生成视图,可以将同一地图视为不同类型。 另一个优势是,整个对象层次结构都存在于一个Map中,这意味着可以使用现有的库(例如Google的gson工具 )轻松进行序列化和反序列化。 如果希望数据是不可变的,则只需将内部映射包装在构造函数中的unmodifiableMap()中,即可保护整个层次结构。

一个缺点是它不如常规的bean结构安全。 可以通过多个接口从多个位置修改组件,这可能会使代码的可测试性降低。 因此,在大规模实施此模式之前,应权衡利弊。

  • 如果要查看实际的抽象文档模式示例,请查看Speedment项目的源代码,其中该项目管理有关用户数据库的所有元数据。

翻译自: https://www.javacodegeeks.com/2016/02/the-best-of-both-worlds.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值