新闻详情

新闻详情

首页 / 资讯中心 / 详情

Java 抽象文档模式(Abstract Document Pattern)深入解析:在 java-design-patterns 中实现灵活的动态数据结构

发布时间:2026/10/1 20:52:00来源:尧图网络
Java 抽象文档模式(Abstract Document Pattern)深入解析:在 java-design-patterns 中实现灵活的动态数据结构
示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载抽象文档Abstract Document是 java-design-patterns 仓库中一个极具实用价值的结构型设计模式它解决的是强类型语言中既要动态灵活地处理树状、层次化的数据又不丢失类型安全这一经典矛盾。本文将以仓库abstract-document模块为骨架结合完整源码与测试用例讲解该模式的核心意图、trait 机制、程序化实现以及在实际项目中何时使用、收益与代价让你能够把这一模式直接应用到 CMS、文件系统、电商、配置管理等场景。Abstract Document 模式类图模式的意图Intent抽象文档模式的意图是为各种文档类型定义一个统一接口以一致的方式处理层次化、树状的数据结构。它将文档的核心结构与具体的数据格式解耦使得系统可以在运行时动态更新属性而无需修改既有类定义简化维护——新增字段时不需要动实体类也不需要重写序列化逻辑在一个强类型语言中为松散类型的 key-value 存储提供类型安全的访问视图。简单来说正如 README 中用大白话说的Abstract Document 模式允许我们给对象附加属性而对象本身并不需要预先知道这些属性attaching properties to objects without them knowing about it。例如 README.md 所引用的 Wikipedia 定义这是一种面向对象的结构型设计模式用于在松散类型的键值存储中组织对象并通过类型化视图暴露数据……在强类型语言中新属性可以在对象树上随时添加同时不丢失类型安全的支持其实现利用了 trait 将类的不同属性分离到不同接口中。真实世界案例图书馆系统考虑一个使用 Java 抽象文档模式实现的图书馆系统图书可能有多种形态和属性——纸质书、电子书、有声书。每种形态都有独特属性纸质书有页数、电子书有文件大小、有声书有时长。抽象文档模式让图书馆系统能够灵活地管理这些不同形态通过该模式系统可以动态地存取属性而不必为每种图书类型维护僵化的类结构未来新增图书形态或属性时也无需对代码库做重大改动。这正是属性集合随时间演化、不同实体共享部分属性又各具特性类问题的典型写照——抽象文档模式把这种演化成本从改类降为改数据。核心组件剖析从源码看模式如何运作abstract-document模块的源码组织在 src/main/java/com/iluwatar/abstractdocument 下共由四类角色构成1.Document接口——统一契约Document.java 定义了三个核心方法Void put(String key, Object value)写入一个键值对属性返回Void而非void便于链式/流式语义Object get(String key)按 key 读取属性值不存在时返回nullT StreamT children(String key, FunctionMapString, Object, T constructor)读取某个 key 下的子文档列表并通过constructor函数将每个子 Map 转换为目标类型返回StreamT。注意children返回的是Stream 而非 List——这正是模式的巧妙之处它把子节点集合延迟为惰性流配合Part::new这类构造器引用可随时对子节点做 filter/map 等操作且天然规避了空集合的判空问题见下文AbstractDocument实现。2.AbstractDocument抽象类——属性 Map 的载体AbstractDocument.java 是模式的发动机它用一个MapString, Object作为唯一的内部状态构造器强制要求属性 Map 非空Objects.requireNonNull(properties, properties map is required)防止空指针隐患put/get直接委托给 Map 操作语义与java.util.Map完全一致同一个 key 再次put即为更新覆盖children的实现是理解本模式的关键一行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); }它把存储在某个 key 下的ListMapString,Object展开为子元素流再逐个经childConstructor转成子文档对象。由于使用了Stream.ofNullable与findAny().stream()当 key 不存在或值为 null 时返回空 Stream绝不会抛 NPE——这一容错特性被测试 AbstractDocumentTest.java 显式验证shouldRetrieveEmptyStreamForNonExistingChildren断言 count 为 0。此外AbstractDocument还覆写了toString()将内部属性 Map 格式化为类名[ [key : value], ... ]的可读字符串方便调试与日志输出。3.Property枚举——属性名的字符串常量池Property.java 定义了PARTS、TYPE、PRICE、MODEL四个枚举常量。它解决的是字符串魔法值问题所有 key 都通过Property.TYPE.toString()等表达式引用拼写错误在编译期即可暴露也便于全局统一重构。4. trait 接口——类型安全的静态视图模式的关键在于用一组以Has开头命名的接口trait把属性访问固化成类型安全的方法。每个 trait 继承Document并通过 default 方法读写特定 keypublic 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); } }要点有三返回Optional标量属性type/price/model用Optional.ofNullable包装明确表达属性可能缺失调用方必须显式处理空值如orElseThrow或orElse返回Stream集合属性parts返回惰性流天然支持后续的流式处理返回类型即契约getType()强转String、getPrice()强转Number属性一旦写入就必须符合类型约定否则会在读取时抛出ClassCastException——这是动态存储 静态视图中类型安全的落点。5. 实体类——零业务逻辑的组合器有了 trait实体类就变得极简。例如 Part.javapublic class Part extends AbstractDocument implements HasType, HasModel, HasPrice { public Part(MapString, Object properties) { super(properties); } }Car.java 同理只是换了一组 traitHasModel, HasPrice, HasParts。实体类只是继承 AbstractDocument 组合所需 trait的空壳——所有能力都来自接口默认方法和抽象基类。这意味着要新增一种实体只需写一个几行的类要让实体支持新属性只需新增一个 trait 接口并把它加进 implements 列表完全符合开闭原则。程序化示例组装一辆动态的汽车现在用一个完整例子串起全部组件。假设汽车由多个零件构成但我们不确定某辆具体汽车是否拥有全部零件——这正是动态且高度灵活的数据形态。先构造零件属性与整车属性见 App.javavar 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)));注意两个细节零件本身是Map.of(...)没有实例化为对象直到getParts()时才由Part::new构造器引用惰性转成Part对象p.getType().orElse(null)展示了 Optional 的兜底用法——即使某零件缺少 type 属性遍历也不会中断。运行主类入口在 App.javapom.xml中通过 maven-assembly-plugin 将其声明为mainClass后标准输出如下07:21:57.391 [main] INFO com.iluwatar.abstractdocument.App -- Constructing parts and car 07:21:57.393 [main] INFO com.iluwatar.abstractdocument.App -- Here is our car: 07:21:57.393 [main] INFO com.iluwatar.abstractdocument.App -- - model: 300SL 07:21:57.393 [main] INFO com.iluwatar.abstractdocument.App -- - price: 10000 07:21:57.394 [main] INFO com.iluwatar.abstractdocument.App -- - parts: 07:21:57.395 [main] INFO com.iluwatar.abstractdocument.App -- wheel/15C/100 07:21:57.395 [main] INFO com.iluwatar.abstractdocument.App -- door/Lambo/300可以看到无论顶层车还是嵌套零件读取时都用的是看起来像静态类型的getModel()/getPrice()方法而背后完全是动态的 Map 存储。如何运行与验证abstract-document是 Maven 多模块项目 pom.xml 下的一个独立模块artifactId: abstract-document版本跟随父工程。在仓库根目录可通过以下命令运行测试与主程序使用仓库自带的 Maven Wrappermvnw# 运行该模块的全部单元测试 ./mvnw -pl abstract-document -am test # 打包含依赖的可执行 jar ./mvnw -pl abstract-document -am package # 运行演示主类 java -jar abstract-document/target/abstract-document-1.26.0-SNAPSHOT-jar-with-dependencies.jar模块依赖slf4j-api与logback-classic用于日志输出测试框架为 JUnit 5见 pom.xml。测试用例是理解模式行为的最佳佐证AbstractDocumentTest.java 覆盖了put/get存取、key 更新覆盖、children 流展开、不存在的 key 返回空流、嵌套文档存取以及构造时传入 null 会抛NullPointerException等边界场景DomainTest.java 验证了Car与Part的端到端行为零件属性可正确读出getType()/getModel()/getPrice()整车的getParts().count()能正确统计两个零件。何时使用抽象文档模式抽象文档模式尤其适合管理共享部分公共属性、又各自拥有独有属性的多类文档的场景。README 列举的典型场景包括内容管理系统CMS文章、图片、视频等不同类型内容共享创建时间、作者、标签等属性又各有图片尺寸、视频时长等专属属性文件系统文档、图片、音频、目录等不同文件类型统一访问文件大小、创建日期同时允许图片分辨率、音频时长等差异属性电商系统实物商品、数字下载、订阅服务共享名称、价格、描述又各有运费重量、下载链接等独有属性医疗记录系统人口学信息、病史、检验结果、处方等各类数据共享患者 ID、出生日期又容纳检验结果、用药方案等专有字段配置管理不同类型配置元素各有其属性集合模式可统一存取与操作教育平台文本、视频、测验、作业等学习资料共享标题、作者、发布日期又各有视频时长、作业截止日期等差异属性项目管理工具待办事项、里程碑、问题单等任务类型处理通用属性任务名、负责人的同时承载里程碑日期、问题优先级等专属属性。判断是否适用可以看这几个信号均出自 README文档具有多样化且持续演化的属性结构动态添加新属性是常见需求将数据访问与具体格式解耦至关重要代码库的可维护性与灵活性是核心诉求。如果属性结构稳定、变化频率极低或者对类型安全与编译期检查有极高要求且不能容忍运行时强转则常规的静态类层次结构可能更合适。收益与代价Benefits and Trade-offs收益灵活性Flexibility容纳千变万化的文档结构与属性实体类不随属性变化而改动可扩展性Extensibility新增属性只需新增 trait 接口并组合不破坏既有代码可维护性Maintainabilitytrait 接口与基类分离关注点代码整洁、职责清晰可复用性Reusability类型化视图trait可被多个实体复用访问特定类型属性的代码一次编写、处处生效。代价复杂度Complexity需要定义接口trait与视图层相比直接使用 POJO 增加了实现开销性能Performance相比直接字段访问Map 查找、Optional 包装与流式处理会带来轻微性能损耗。此外还值得补充一点权衡属性值多为强转访问类型错误被推迟到运行时因此需要配合完善的测试本模块的 DomainTest.java 与 AbstractDocumentTest.java 正是为此而生。参考资料Design Patterns: Elements of Reusable Object-Oriented SoftwareGoF 经典四人组著作Java Design Patterns: A Hands-On Experience with Real-World ExamplesPattern-Oriented Software Architecture Volume 4: A Pattern Language for Distributed ComputingPatterns of Enterprise Application ArchitectureMartin FowlerAbstract Document PatternWikipediaDealing with PropertiesMartin Fowler小结抽象文档模式在 java-design-patterns 仓库中是一个教科书级的实现Document接口定义契约AbstractDocument用单一 Map 承载动态数据HasXxxtrait 提供类型安全视图实体类则退化为零逻辑的组合壳。它把数据是动态树与访问是静态契约这对看似冲突的需求优雅地统一起来——如果你正在面对属性不断演化、实体形态多样的数据模型这个模式值得优先纳入你的工具箱。赞分享示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载相关推荐pypdf 安装完整指南一条命令装好5 分钟上手 PDF 处理pypdf 安装完整指南一条命令装好5 分钟上手 PDF 处理 pypdf 安装是 Python 做 PDF 处理的第一步也是门槛最低的一步。pypdf示例工程教程Java 桥接模式Bridge Pattern实战指南在 java-design-patterns 中解耦抽象与实现Java 桥接模式Bridge Pattern实战指南在 java design patterns 中解耦抽象与实现 桥接模式Bridge是 GoF示例工程教程Java 组合模式Composite Pattern实战指南用 java-design-patterns 构建灵活的树形结构Java 组合模式Composite Pattern实战指南用 java design patterns 构建灵活的树形结构 组合模式Composite示例工程教程上一篇最完整Neon教程从环境搭建到第一个Rust Node.js模块开发下一篇如何在老旧平板上流畅运行Weylus终极性能优化与兼容性调整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Excel均值曲线图表:重复数据平均、误差线与动态数据源 2026/10/2 14:32:45

Excel均值曲线图表:重复数据平均、误差线与动态数据源

数据处理这活儿干久了,你会发现一个规律:单条曲线基本没法看。同一台设备连测五遍,五条线七拐八拐,你盯着屏幕半天也说不清到底哪个才是"真实趋势"。这时候大概率要请出均值曲线图表——把多组重复数据在每个采样点上取…

阅读更多 →
美团三合一系统源码架构与部署全解析:从订单表设计到实战避坑 2026/10/2 14:32:45

美团三合一系统源码架构与部署全解析:从订单表设计到实战避坑

简介:一套基于PHP开发的美团、京东、拼多多三合一代付系统源码,附带视频教程与完整搭建说明,面向需要快速搭建H5自助下单代付平台的站长、技术运维或PHP二次开发者。系统自带倒计时与代付人头像展示,前端采用移动端H5页面&#xf…

阅读更多 →
美团三合一系统源码落地指南:从开放平台接入到避坑实践 2026/10/2 14:32:45

美团三合一系统源码落地指南:从开放平台接入到避坑实践

简介:这套2026年最新发布的美团三合一源码,是一套基于PHP开发的多平台代付系统,支持美团、京东、拼多多三条代付通道,内置倒计时功能,并适配手机端H5自助下单、代付人头像展示等交互场景,适合需要搭建代付平…

阅读更多 →
连接条件下推代价模型:从设计到落地的查询优化实践 2026/10/2 14:32:45

连接条件下推代价模型:从设计到落地的查询优化实践

复杂查询跑不动,是搞数据的人最常遇到的噩梦。 一张报表SQL,10张表join,几十个过滤条件,跑一次小半个小时。业务方催得急,运维盯着CPU报警,你盯着执行计划发呆。这种时候,绝大多数人第一反应是…

阅读更多 →
微信聊天记录解析:SQLite与SQLCipher解密导出备份指南 2026/10/2 14:32:45

微信聊天记录解析:SQLite与SQLCipher解密导出备份指南

简介:面向需要管理个人微信数据的用户,这份资源提供了一套完整的微信数据库解析工具,覆盖聊天记录提取、联系人导出、群组信息获取、数据库解密、内容备份与恢复、数据挖掘等核心功能,同时也支持PC端与手机端微信数据的同步管理。…

阅读更多 →
OpenClaw 进阶:多 Agent 协作与任务编排实战——用 TaoToken 统一 Key 打通运行时 2026/10/2 14:32:39

OpenClaw 进阶:多 Agent 协作与任务编排实战——用 TaoToken 统一 Key 打通运行时

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉