尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Java 设计模式解析:Abstract Document 抽象文档模式——在类型安全与动态属性之间取得平衡

Java 设计模式解析:Abstract Document 抽象文档模式——在类型安全与动态属性之间取得平衡 示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载本篇技术指南基于 java-design-patterns 仓库中的 Abstract Document抽象文档模式模块系统讲解这一结构性设计模式的动机、接口与抽象类设计、trait特性接口机制、实体组装方式及测试验证路径。读完本文你将掌握如何在不牺牲强类型语言类型安全的前提下为对象树动态挂载任意属性并能直接参照仓库源码复现完整示例。模式概述为什么需要 Abstract Document目的Abstract Document 模式的官方定位是使用动态属性并在保持类型安全的同时实现非类型化语言如动态语言所具备的灵活性。在 Java 这类强类型语言中对象通常被建模为固定的类结构——字段在编译期就确定。然而现实中存在大量结构不确定的场景一个对象可能拥有全部属性也可能只拥有其中一部分属性的集合甚至可能在运行时才被决定。Abstract Document 模式正是为这类场景而生它让对象在不知道自己具体有哪些属性的情况下依然能够被附加属性同时通过 trait 接口为这些松散存储的键值对提供静态、类型安全的访问视图。核心思想该模式的核心组织方式是把对象建模为松散类型的键值存储再用一组类型化视图trait 接口对外公开数据。所有数据都保存在底层的MapString, Object中而 trait 接口通过default方法提供类型化的读取入口。这样既保留了动态添加属性的自由度又让调用方在编译期就能获得类型安全的访问能力。真实世界示例文档以汽车为例一辆汽车由多个零件组成但我们事先并不知道某辆特定汽车是否拥有全部零件还是只有其中一部分。汽车本身是动态且非常灵活的——这恰好与键值存储 trait 视图的设计天然吻合。在仓库中Car与Part两个实体类就是这一思想的落地实现见 Car.java 与 Part.java。架构与角色划分从 abstract-document.urm.puml 所示的类图可以清晰看到模式的三层结构角色类/接口职责文档契约Document.java定义put/get/children三个基础操作是所有文档对象的统一入口骨架实现AbstractDocument.java持有MapString, Object属性映射完整实现Document接口trait 视图HasType/HasModel/HasPrice/HasParts各自以default方法暴露某一类属性的类型化读取属性键定义Property.java用枚举集中定义属性键名PARTS、TYPE、PRICE、MODEL避免魔法字符串领域实体Car/Part继承AbstractDocument并选择性实现 trait 接口组装出静态外观值得注意的设计细节是trait 接口本身继承自Document如HasType extends Document因此任何实现 trait 的类都必然具备文档能力而Car与Part则通过接口组合interface composition获得不同类型的属性视图Car拥有HasModel、HasPrice、HasPartsPart拥有HasType、HasModel、HasPrice。从接口到实现逐步拆解核心代码第一步定义 Document 契约原文档给出了 Document.java 的完整定义它只包含三个方法public interface Document { Void put(String key, Object value); Object get(String key); T StreamT children(String key, FunctionMapString, Object, T constructor); }三个方法各司其职put向文档写入一个键值对返回类型为装箱的Void返回null以匹配Map.put的返回语义get按键读取属性值键不存在时返回nullchildren按键读取一个子文档列表并对列表中每个子映射调用构造器函数constructor产出类型化子对象的StreamT。这是模式实现对象树能力的关键。第二步AbstractDocument 骨架实现AbstractDocument.java 是该模式的骨架实现public abstract class AbstractDocument implements Document { private final MapString, Object documentProperties; protected AbstractDocument(MapString, Object properties) { Objects.requireNonNull(properties, properties map is required); this.documentProperties properties; } Override public Void put(String key, Object value) { documentProperties.put(key, value); return null; } Override public Object get(String key) { return documentProperties.get(key); } Override public T StreamT children(String key, FunctionMapString, Object, T childConstructor) { return Stream.ofNullable(get(key)) .filter(Objects::nonNull) .map(el - (ListMapString, Object) el) .findAny() .stream() .flatMap(Collection::stream) .map(childConstructor); } Override public String toString() { return buildStringRepresentation(); } // ... }源码中有几个值得展开的实现细节构造器强制非空校验Objects.requireNonNull(properties, properties map is required)在构造时即拒绝null属性映射防止后续put/get出现难以排查的空指针。这一点在单元测试shouldHandleExceptionDuringConstruction中得到了验证见 AbstractDocumentTest.java。children的流式管道Stream.ofNullableJava 9 引入将get(key)的结果安全包装——当键不存在时得到空流随后强转为ListMapString, Object经findAny().stream()把Optional拍平成流再flatMap(Collection::stream)展开子元素列表最后逐个通过childConstructor构造类型化子对象。当键缺失或值不是列表时该方法返回空流而非抛出异常这是测试shouldRetrieveEmptyStreamForNonExistingChildren所保证的行为。toString的可读性buildStringRepresentation()将文档输出为类名[键 : 值, ...]形式便于调试时直接观察文档内容测试shouldIncludePropsInToString验证了键值确实包含在字符串表示中。第三步Property 枚举统一属性键为了避免散落的魔法字符串仓库用 Property.java 集中定义属性键public enum Property { PARTS, TYPE, PRICE, MODEL }所有 trait 方法均通过Property.X.toString()生成键名访问底层 Map实现键名一处定义、处处复用。第四步trait 接口提供类型安全视图trait 接口是 Abstract Document 模式的精髓——它们用default方法把动态的键值访问封装成静态的、类型安全的读取 API并在值缺失时返回Optionalpublic interface HasType extends Document { default OptionalString getType() { return Optional.ofNullable((String) get(Property.TYPE.toString())); } } public interface HasPrice extends Document { default OptionalNumber getPrice() { return Optional.ofNullable((Number) get(Property.PRICE.toString())); } } public interface HasModel extends Document { default OptionalString getModel() { return Optional.ofNullable((String) get(Property.MODEL.toString())); } } public interface HasParts extends Document { default StreamPart getParts() { return children(Property.PARTS.toString(), Part::new); } }对应源码见 HasType.java、HasModel.java、HasPrice.java 与 HasParts.java。可以看到标量属性type/model/price走getOptional.ofNullable路径集合属性parts则走children 构造器引用Part::new路径——两者在源码中的分工非常清晰。第五步组装领域实体 Car有了骨架与 trait领域实体只需极少的代码即可完成组装public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts { public Car(MapString, Object properties) { super(properties); } }Car只声明我有哪些属性视图model、price、parts至于属性映射里具体存了什么、有多少个零件全部交由运行时动态决定。这也是模式名称中Abstract的由来——对象结构在抽象层面是确定的具体属性却是开放的。完整运行示例构建一辆汽车App.java 提供了可直接运行的完整示例演示如何用嵌套的不可变 Map 构造出带零件的汽车对象树LOGGER.info(Constructing parts and car); var wheelProperties Map.of( Property.TYPE.toString(), wheel, Property.MODEL.toString(), 15C, Property.PRICE.toString(), 100L); var doorProperties Map.of( Property.TYPE.toString(), door, Property.MODEL.toString(), Lambo, Property.PRICE.toString(), 300L); var carProperties Map.of( Property.MODEL.toString(), 300SL, Property.PRICE.toString(), 10000L, Property.PARTS.toString(), List.of(wheelProperties, doorProperties)); var car new Car(carProperties); LOGGER.info(Here is our car:); LOGGER.info(- model: {}, car.getModel().orElseThrow()); LOGGER.info(- price: {}, car.getPrice().orElseThrow()); LOGGER.info(- parts: ); car.getParts().forEach(p - LOGGER.info(\t{}/{}/{}, p.getType().orElse(null), p.getModel().orElse(null), p.getPrice().orElse(null)));运行后输出如下Constructing parts and car Here is our car: model: 300SL price: 10000 parts: wheel/15C/100 door/Lambo/300这段代码的运行机制值得梳理嵌套属性映射wheelProperties与doorProperties分别是两个零件子映射carProperties通过Property.PARTS.toString()键把它们作为List挂到汽车文档下形成一棵两层对象树Map.of的不可变性示例使用 Java 9 的Map.of/List.of构造属性映射这些集合不可变恰好符合属性一次性注入的典型用法Map.of的重载对参数数量有限制若属性较多需改用HashMap类型安全读取car.getModel()、car.getPrice()返回Optional配合orElseThrow()在属性缺失时快速失败car.getParts()返回StreamPart每个Part又通过自己的 trait 接口暴露getType()/getModel()/getPrice()从源码的Stream.ofNullable、Map.of、var等 API 可以推断该实现要求 Java 9 的运行时环境。类图Abstract Document 模式类图Document 接口、AbstractDocument 抽象类与 Car/Part 领域模型及 HasType、HasModel、HasPrice、HasParts 四个 trait 接口的关系如上图源文件 abstract-document.png对应 PlantUML 源 abstract-document.urm.puml所示AbstractDocument实现DocumentCar继承AbstractDocument并实现HasModel/HasPrice/HasPartsPart继承AbstractDocument并实现HasType/HasModel/HasPrice四个 trait 接口均继承自Document。这一结构使得任意组合 trait 即可拼装出不同能力视图的实体而无需改动底层存储。测试与验证模式行为的可证明边界仓库为该模块提供了两套单元测试可作为理解模式行为的权威参考AbstractDocumentTest.java直接对AbstractDocument骨架做契约验证覆盖六类行为——shouldPutAndGetValueput后可get回相同值shouldRetrieveChildren放入两个子映射后children返回数量为 2 的流shouldRetrieveEmptyStreamForNonExistingChildren键不存在时返回空流而非异常shouldIncludePropsInToStringtoString中包含键与值shouldHandleExceptionDuringConstruction传入null属性映射触发NullPointerExceptionshouldPutAndGetNestedDocument与shouldUpdateExistingValue文档可嵌套、值可原地更新。DomainTest.java对领域实体做集成验证——shouldConstructPart校验Part三个 trait 读取值正确shouldConstructCar校验Car的 model/price 读取与getParts().count() 2直接印证了对象树的组装逻辑。该模块自带独立的 pom.xml仓库根目录提供 Maven Wrappermvnw。在具备 Maven 环境的前提下进入abstract-document目录执行mvn test或通过根目录./mvnw运行对应模块测试即可复现上述全部行为验证。适用性何时选择 Abstract Document原文档明确了该模式的三个典型适用场景需要即时运行时新增属性时对象的属性集合在编译期无法穷尽属性可以随业务动态增减需要一种灵活的方式以树状结构组织领域domain时父子节点结构不确定例如本文的汽车—零件嵌套模型需要更松散的耦合系统时对象之间通过键值契约而非强类型字段耦合便于独立演化和组合。对应地当对象结构完全静态、属性有限且确定时直接使用普通 POJO 即可不必引入本模式。总结与延伸阅读Abstract Document 模式用底层Map存储 上层 trait 视图的分层设计在 Java 的强类型约束下复刻了动态语言的灵活性Document与AbstractDocument保证通用文档能力Property枚举统一键名HasXxxtrait 提供类型安全访问Car/Part等实体则通过接口组合自由拼装视图。其思想可追溯至 Martin Fowler 关于属性处理的讨论以及《Pattern-Oriented Software Architecture Volume 4》等经典资料详见原文档的参考资料章节读者可按名查阅。对希望继续深挖的读者建议按以下路径在仓库中研读先读 AbstractDocument.java 的children管道实现再对照 AbstractDocumentTest.java 验证其边界行为最后通过 App.java 观察嵌套文档树的实际构造与读取即可完整掌握该模式的设计与落地。赞分享示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载相关推荐Java 设计模式之 Abstract Document 抽象文档模式在类型安全与动态属性之间取得平衡Java 设计模式之 Abstract Document 抽象文档模式在类型安全与动态属性之间取得平衡 Abstract Document抽象文档模式是示例工程教程Java 设计模式之 Abstract Document抽象文档模式在强类型语言中实现动态属性与类型安全的平衡Java 设计模式之 Abstract Document抽象文档模式在强类型语言中实现动态属性与类型安全的平衡 导读 Abstract Document示例工程教程Java 设计模式实战Abstract Document抽象文档模式——在类型安全下实现动态属性与灵活数据树Java 设计模式实战Abstract Document抽象文档模式——在类型安全下实现动态属性与灵活数据树 Abstract Document抽象文档示例工程教程上一篇Fleet 4.25.0 深度解读critical 策略、账户活动审计与 Windows MDM 可见性下一篇Luigi 工作流构建指南深入理解 Task、Target 与 Parameter 三大核心抽象创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表