ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java 设计模式实战:Abstract Document(抽象文档)模式——在类型安全下实现动态属性与灵活数据树

Java 设计模式实战:Abstract Document(抽象文档)模式——在类型安全下实现动态属性与灵活数据树 示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载Abstract Document抽象文档是一种结构型设计模式它通过弱类型键值存储 强类型 trait 视图的组合让 Java 这种强类型语言能够像动态语言一样随时向对象树添加新属性同时保留编译期的类型安全。本文以 abstract-document 模块的完整源码与测试为佐证从意图、核心抽象、trait 接口、Car 实战示例、适用场景到收益取舍带你彻底掌握该模式。模式的意图Intent在强类型语言中处理层次化、树状的数据结构往往很繁琐每种节点都要定义一套固定的字段新增属性意味着改类、改构造器、改序列化逻辑。Abstract Document 模式的核心意图是使用动态属性key-value 存储获得弱类型语言的灵活性同时通过 trait特质接口保持类型安全。通俗地说该模式允许对象在不知道自己拥有哪些属性的情况下被附加属性。数据以MapString, Object的形式松散存放而对外访问则通过一组小而专一的 trait 接口以静态、类型安全的方式呈现。正如维基百科对 Abstract Document 模式的定义它是一种面向对象的结构型设计模式用于把对象组织在松散类型的键值存储中并通过类型化视图typed views暴露数据。其目的是在强类型语言中让组件之间获得高度的灵活性——对象树可以在运行时动态新增属性同时不丧失类型安全支持。模式利用 traits 把类的不同属性拆分为不同接口。真实世界类比一辆零件不确定的汽车文档用汽车作为示例一辆车由多个零件组成但我们事先并不知道某辆具体车到底拥有全部零件还是只拥有其中一部分。车是动态的、极其灵活的——这正是 Abstract Document 模式要解决的场景。某些属性如MODEL、PRICE是静态的、一定存在的另一些属性如PARTS列表是动态的、数量与结构不确定的每个零件Part自身又拥有TYPE、MODEL、PRICE等属性构成一棵递归的树。用一句话概括Abstract Document 模式让对象在不知情的情况下被附加属性。核心抽象Document 接口与 AbstractDocument 基类模式的地基是Document接口与AbstractDocument抽象类源码位于 Document.java 与 AbstractDocument.java。Document接口只定义三个操作覆盖了单值读写与子文档遍历两大能力public interface Document { /** * Puts the value related to the key. * * param key element key * param value element value * return Void */ Void put(String key, Object value); /** * Gets the value for the key. * * param key element key * return value or null */ Object get(String key); /** * Gets the stream of child documents. * * param key element key * param constructor constructor of child class * return child documents */ T StreamT children(String key, FunctionMapString, Object, T constructor); }三个方法的职责非常清晰方法签名作用putVoid put(String key, Object value)向属性 Map 写入键值返回Void以允许接口默认方法链式使用getObject get(String key)按键读取原始值不存在时返回null注意接口约定返回Object由上层 trait 负责转型childrenT StreamT children(String key, FunctionMapString, Object, T constructor)按键取出子文档列表并用构造器函数把每个子 Map 转换为类型化对象返回StreamTAbstractDocument提供了基于Map的默认实现它用Objects.requireNonNull强制校验属性 Map 非空源码 AbstractDocument.javapublic 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); } }children是模式中最精妙的一段逻辑可以逐层拆解其流水线源码 AbstractDocument.javaStream.ofNullable(get(key))把可能为null的原始值包装成空流或单元素流天然处理该键不存在的情况filter(Objects::nonNull)过滤掉null即使键存在但值为空也不报错map(el - (ListMapString, Object) el)把原始值强制转型为子文档列表——这是该实现中唯一一处信任数据格式的强转点findAny().stream()把Optional再转回流避免空 OptionalflatMap(Collection::stream)摊平多个子文档map(childConstructor)用调用方传入的构造器引用如Part::new把每个子Map构造成类型化对象。AbstractDocument还重写了toString()遍历所有属性拼成类名[键 : 值, ...]的调试友好格式源码 AbstractDocument.java方便日志输出。trait 视图让动态存储看起来是静态的光有 Map 存取还不够——直接写car.get(MODEL)容易拼错键名、丢失类型。模式的第二步是用trait 接口为特定属性提供类型安全的访问视图。仓库中用枚举Property统一管理键名源码 Property.java/** Enum To Describe Property type. */ public enum Property { PARTS, TYPE, PRICE, MODEL }每个 trait 都是一个继承Document的接口通过default方法内部调用get(...)完成取值与转型并把结果包装为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、HasPrice.java、HasModel.java、HasParts.java。观察要点单值属性TYPE、PRICE、MODEL返回OptionalT键缺失或值为null时返回Optional.empty()调用方用orElseThrow()/orElse(default)显式决策规避空指针集合属性PARTS返回StreamPart直接复用children(...)键缺失时得到空流可放心forEachtrait 是可组合的一个实体可以实现任意多个 trait按需点亮自己拥有的属性视图这正是模式分离关注点的精髓。组装实体Car 与 Part有了地基与 trait实体类变得异常简洁——它们几乎不需要任何业务逻辑只声明我拥有哪些视图并转发构造参数源码 Car.java 与 Part.java/** Car entity. */ public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts { public Car(MapString, Object properties) { super(properties); } } /** Part entity. */ public class Part extends AbstractDocument implements HasType, HasModel, HasPrice { public Part(MapString, Object properties) { super(properties); } }注意Car实现了HasModel, HasPrice, HasParts而Part实现了HasType, HasModel, HasPrice——两棵不同层级、不同属性集合的节点共享同一套AbstractDocument存储机制无需任何继承耦合。完整实战构造一辆三层结构的汽车程序入口在 App.java完整演示了零件 Map → 车辆 Map → Car 对象 → 递归遍历的全流程public static void main(String[] args) { 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))); }运行主类仓库为 Maven 多模块结构可通过根目录的./mvnw执行abstract-document模块Windows 下用mvnw.cmd后的程序输出Constructing parts and car Here is our car: - model: 300SL - price: 10000 - parts: wheel/15C/100 door/Lambo/300这段代码值得逐行体会wheelProperties、doorProperties是弱类型的Map键名来自Property枚举值可以是String或LongcarProperties的PARTS键直接放入List.of(wheelProperties, doorProperties)形成树状嵌套读取端car.getModel()、car.getPrice()、car.getParts()全部走 trait 接口强类型且无需关心底层 Map 细节零件通过p.getType().orElse(null)取值缺属性也不会抛异常。类图traits 与领域对象的关系下图展示了该模式在仓库中的类结构——Document作为统一抽象AbstractDocument提供实现HasType/HasPrice/HasModel/HasParts四个 trait 从Document派生Car与Part组合继承多个 trait对应 PlantUML 源文件位于 abstract-document.urm.puml可自行生成/查看高分辨率版本。测试验证模式行为的边界保障仓库的测试代码直接印证了模式的关键行为边界值得阅读 AbstractDocumentTest.java 与 DomainTest.java读写与更新shouldPutAndGetValue、shouldUpdateExistingValue验证put/get语义同键覆盖即更新子文档流shouldRetrieveChildren验证children(...)能把 List转换为对象流并计数shouldRetrieveEmptyStreamForNonExistingChildren验证键不存在时返回空流而非异常嵌套文档shouldPutAndGetNestedDocument验证任意对象包括另一个Document实例都可作为属性值嵌套空属性防御shouldHandleExceptionDuringConstruction验证构造时传入nullMap 会抛出NullPointerException来自Objects.requireNonNull领域组装DomainTest.shouldConstructCar验证Car能容纳两个空 Map 零件并正确返回数量为 2 的零件流。这些测试精确刻画了模式的两大承诺动态性运行时增删属性与健壮性缺失键不崩溃。何时使用 Abstract Document 模式从文档与源码可以总结出以下适用信号需要立即运行时添加新属性如配置文件、插件化数据结构属性集不固定想把领域组织成树状结构文档/零件/子文档递归嵌套且每层属性集合不同希望系统更松散耦合数据访问与具体格式解耦新增一种文档类型不必改动既有存储逻辑文档/实体的属性结构持续演进共享属性如创建时间、作者、价格与独有属性如视频时长、图片分辨率并存。更具体的应用场景包括内容管理系统文章/图片/视频属性各异、文件系统文档/图片/音频/目录统一管理、电商平台实物/数字商品/订阅、医疗记录系统人口学/病史/检验结果/处方、配置管理、教育平台文本/视频/测验/作业与项目管理工具待办/里程碑/问题单等。收益与代价Trade-offs收益Benefits灵活性兼容多变、演进的文档结构与属性集合可扩展性动态添加新属性而无需破坏既有代码——加一个 trait、加一个枚举值即可可维护性关注点分离存储 vs 视图代码更清晰、更易适配可复用性类型化视图trait可被多个实体复用访问特定属性类型时无需重复逻辑。代价Trade-offs复杂度需要为属性定义接口与视图增加实现开销简单场景下显得过度设计性能相比直接字段访问Map 查找 强转 Optional 包装会带来轻微性能损耗不适合对单次访问延迟极敏感的热路径类型安全是约定式的children内部的强转依赖调用方传入正确格式的 List若数据源不可信需在构造时校验。小结Abstract Document 模式给出了一个优雅的折中存储层用弱类型的 Map 换取无限动态性访问层用 trait 接口换回强类型的编译期安全。Document/AbstractDocument是地基Property枚举统一键名HasXxxtrait 提供类型化视图Car/Part通过接口组合自由拼装属性集。如果你正在设计配置体系、内容模型或任何属性不断演进的树状数据可以复用本模块的源码骨架与测试范式快速落地。延伸阅读参考该模式概念源自《Pattern-Oriented Software Architecture Volume 4: A Pattern Language for Distributed Computing (v. 4)》并可与 Martin Fowler 关于处理属性Dealing with Properties的思路互相印证。赞分享示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载相关推荐Java 设计模式实战Abstract Document 抽象文档模式——用类型安全的方式动态管理树状属性结构Java 设计模式实战Abstract Document 抽象文档模式——用类型安全的方式动态管理树状属性结构 本篇技术指南以 java design pat示例工程教程Amplication amplication/util/git 实战指南GitClientService 统一封装与多 Git 平台接入Amplication amplication/util/git 实战指南GitClientService 统一封装与多 Git 平台接入 导读 ampl示例工程教程Abstract Document 抽象文档模式实战在强类型 Java 中实现动态属性扩展java-design-patterns 源码详解Abstract Document 抽象文档模式实战在强类型 Java 中实现动态属性扩展java design patterns 源码详解 Abstra示例工程教程上一篇推荐Obsidian PDF革新你的PDF学习与研究体验下一篇【亲测免费】 项目推荐Komorebi——为Linux带来生动壁纸的神器创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表