Java 设计模式之抽象文档模式(Abstract Document):在强类型语言中构建灵活的动态数据结构 示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载导读抽象文档Abstract Document是一种结构型设计模式它通过“弱类型键值存储 强类型视图traits”的组合为 Java 这类静态强类型语言提供了一种处理层级化、树状数据的统一方式。本指南以 java-design-patterns 仓库中 abstract-document 模块 的实现为例从意图、核心接口、trait 视图、完整程序示例到典型应用场景带你掌握如何在不牺牲类型安全的前提下为对象树动态添加任意属性。模式意图用统一接口管理任意文档结构抽象文档模式的出发点非常朴素很多业务实体商品、文件、任务、病历……天然是层级化、树状结构且不同类型的实体既有公共属性又各自拥有独特的专属属性。如果为每种类型都定义一个“刚性”的 Java 类那么新增一种属性就意味着修改类定义、改动所有构造与访问逻辑代码库会迅速变得难以维护。抽象文档模式给出的解法是先定义一个所有文档都遵循的公共接口让对象内部持有一张MapString, Object属性表再通过一组“trait 接口”以静态、类型安全的方式暴露这些属性。用仓库源码的话说它“使得对象能够持有属性映射与任意数量的子对象”同时“利用 traits 的概念实现类型安全并把不同类的属性分离到一组接口中”。该模式被归类为 Structural结构型其核心标签包括 Extensibility可扩展性、Decoupling解耦与 Encapsulation封装对应英文文档见 abstract-document/README.md。通俗理解与核心思想用一个例子来理解一辆汽车由若干零件组成但我们事先并不知道这辆车是否真的拥有全部零件或只拥有其中一部分。汽车是动态的、极其灵活的。抽象文档模式允许你在对象完全不知情的情况下把属性附加到对象上。维基百科对它的定义是一种面向对象的结构型设计模式用于把对象组织成弱类型的键值存储并通过类型化视图暴露数据。其目标是在强类型语言中实现组件间的高度灵活性——既能即时向对象树中添加新属性又不丢失类型安全的支持。该模式利用 traits 把类的不同属性分离到不同的接口中。一句话概括属性是数据存在 Map 里类型是契约写在接口里两者解耦各司其职。核心实现从 Document 接口到 AbstractDocument 基类Document 接口三个方法定义全部契约整个模式的地基是Document接口它只声明了三个方法见 Document.javapublic interface Document { /** 写入 key 对应的属性值 */ Void put(String key, Object value); /** 读取 key 对应的属性值不存在时返回 null */ Object get(String key); /** 取 key 对应的子文档流并用构造函数把每个子 Map 转换为 T */ T StreamT children(String key, FunctionMapString, Object, T constructor); }可以看到put/get面向的是“弱类型的键值存取”而children则是“层级结构的遍历入口”——它接收一个构造函数把子节点的Map还原成具体的领域对象这正是模式中“类型化视图”的枢纽。AbstractDocument属性表的默认实现AbstractDocument是Document的抽象基类位于 AbstractDocument.java它用一个private final MapString, Object documentProperties承载全部属性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); } ... }两个值得注意的实现细节构造器强制非空Objects.requireNonNull(properties, properties map is required)保证每个文档实例都必须携带一张属性表避免空指针在深层调用中潜伏。children的容错管线先用Stream.ofNullable处理 key 不存在的场景直接得到空流不抛异常随后把取到的值安全转型为ListMapString, Object扁平化后逐一交给childConstructor构造子对象。这套写法让“子节点缺失”“子节点为空”都能优雅降级为空流。此外AbstractDocument还重写了toString()按类名[ [key : value], ... ]的格式输出全部属性便于调试与日志打印。Trait 视图把弱类型存取包装成强类型 API仅仅有 Map 是不够的——直接把get(model)的结果强制转型类型安全就无从谈起。抽象文档模式的关键一步是用trait 接口Java 8 的default方法为每个属性建立“静态外观”。仓库中先用枚举统一定义属性键名见 Property.javapublic enum Property { PARTS, TYPE, PRICE, MODEL }然后为每个属性定义一个继承Document的 trait 接口例如 HasType.java、HasParts.java 以及同目录下的HasModel、HasPricepublic 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); } }这套设计的收益是双向的对调用方拿到的是OptionalString、OptionalNumber、StreamPart这样的强类型返回值编译期即可发现转型错误无需手动强转对实现方每个 trait 只声明“我拥有某个属性的读法”具体属性是否存在、以什么类型存储完全由底层的 Map 决定两者彻底解耦。其中HasParts.getParts()使用Part::new作为children的构造函数——这正是前文Document.children方法“接收构造函数、把子 Map 还原为领域对象”的实战用法。组装领域对象Car 与 Part有了基类和 trait领域对象只剩“组合”这一步。汽车Car与零件Part都只是继承AbstractDocument并按需挂载 trait 的空壳类见 Car.java 与 Part.javapublic class Car extends AbstractDocument implements HasModel, HasPrice, HasParts { public Car(MapString, Object properties) { super(properties); } } public class Part extends AbstractDocument implements HasType, HasModel, HasPrice { public Part(MapString, Object properties) { super(properties); } }注意区分两者的“视图面”Car实现了HasModel、HasPrice、HasParts一辆车有型号、价格和零件列表Part实现了HasType、HasModel、HasPrice一个零件有类型、型号和价格。同一套 trait 在不同对象上按需组合这正是“把属性分离到一组接口中”的直接体现——属性的读写能力变成了可插拔的接口集合。完整程序示例构建一辆 300SL 汽车仓库的入口类 App.java 给出了从零构建“车轮 车门 整车”的完整流程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))); }运行后输出Constructing parts and car Here is our car: - model: 300SL - price: 10000 - parts: wheel/15C/100 door/Lambo/300值得玩味的是数据流向车轮和车门各自只是普通MapCar也只是持有这些 Map 的文档对象但当调用car.getParts()时children方法通过Part::new把每个零件 Map 还原成Part实例再借助HasType/HasModel/HasPrice的默认方法逐一取出强类型属性。数据从头到尾是松散的键值结构API 从头到尾是严格的类型契约。如何运行示例该模块是独立的 Maven 子模块位于 abstract-document/pom.xml主类为com.iluwatar.abstractdocument.App。在仓库根目录下执行mvn -pl abstract-document compile exec:java -Dexec.mainClasscom.iluwatar.abstractdocument.App也可先mvn install后在abstract-document目录内运行。模块仅依赖 slf4j-api、logback-classic日志与 junit-jupiter-engine测试无任何第三方框架耦合非常适合作为模式研究的样板。类图总览抽象文档模式类图Document / AbstractDocument 与 HasType、HasModel、HasPrice、HasParts 等 trait 及 Car、Part 领域类上图展示了模式的完整骨架Document接口位于核心AbstractDocument提供默认实现领域类Car/Part各自组合一组 trait 接口Property枚举统一管理属性键名。类图源文件见 abstract-document.uclsUML 工具源与 abstract-document.urm.pumlPlantUML 源。测试佐证模式行为的可验证性模块自带的单元测试 DomainTest.java 直接验证了模式的两种关键行为shouldConstructPart用三键 Map 构造Part断言getType()/getModel()/getPrice()能取回类型、型号、价格shouldConstructCar构造包含两个子 Map 的Car断言getModel()/getPrice()正确且car.getParts().count()返回 2证明children能把嵌套子 Map 全部还原为Part流。测试中使用Map.of()传入空属性 Map 也能正常构建对象取属性时得到空Optional这恰好印证了“对象不必拥有全部属性”的动态特性。此外 AbstractDocumentTest.java 覆盖了基类方法AppTest.java 覆盖了入口程序的运行。执行mvn -pl abstract-document test即可运行全部测试。适用场景什么时候该用抽象文档模式抽象文档模式特别适合“多类文档共享公共属性、又各自拥有独特属性”的场景。原文档给出了七类典型应用结合属性树的特点可以总结为内容管理系统CMS文章、图片、视频共享创建时间、作者、标签但图片有尺寸、视频有时长文件系统文档、图片、音频、目录共享文件大小与创建日期但图片有分辨率、音频有播放时长电商平台实物、数字下载、订阅共享名称、价格、描述但实物有运费重量、数字商品有下载链接医疗病历系统人口学信息、病史、检查结果、处方共享患者 ID 与出生日期同时各自携带专属字段配置管理不同类型的配置项各有属性集但需要统一的读写方式教育平台文本、视频、测验、作业共享标题、作者、发布日期视频有时长、作业有截止日期项目管理工具待办、里程碑、缺陷共享任务名与经办人里程碑有日期、缺陷有优先级。共同特征可以归纳为三条判断标准与英文版 README 的When to Use部分一致文档/实体的属性结构多样且持续演化“运行时动态新增属性”是常见需求需要把数据访问与具体格式解耦。收益与权衡收益灵活性能容纳千变万化的文档结构与属性组合可扩展性新增属性只需新增键值或新增一个 trait 接口不破坏既有代码可维护性属性访问逻辑按关注点分离到 trait 接口中代码更干净、更易演进可复用性类型化视图trait让“按类型取属性”的代码可以在多个文档类之间复用。代价复杂度需要为每个属性/属性组定义接口视图增加了一定的实现开销性能经由 Map 的间接访问与Optional/Stream包装相比直接字段访问会有轻微的性能损耗对绝大多数业务场景可忽略。参考资料与延伸阅读英文原文 READMEabstract-document/README.md模式出处与理论延伸Wikipedia “Abstract Document Pattern”、Martin Fowler《Dealing with properties》、Pattern-Oriented Software Architecture Volume 4、GoF《设计模式》、Fowler《企业应用架构模式》出处详见 abstract-document/README.md 的 References 小节抽象文档模式的价值在于它正视了一个现实业务数据结构永远在变化而类型系统希望我们提前把一切都定死。通过“Map 存数据、接口定类型”的分层设计它让两者握手言和——这正是它在需要高度可扩展性、属性结构不断演化的系统中历久弥新的根本原因。赞分享示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载相关推荐Java 抽象文档模式Abstract Document Pattern深入解析在 java-design-patterns 中实现灵活的动态数据结构Java 抽象文档模式Abstract Document Pattern深入解析在 java design patterns 中实现灵活的动态数据结构 抽示例工程教程Java 设计模式实战Abstract Document 抽象文档模式——用类型安全的方式动态管理树状属性结构Java 设计模式实战Abstract Document 抽象文档模式——用类型安全的方式动态管理树状属性结构 本篇技术指南以 java design pat示例工程教程深入解析 Java 设计模式之 Abstract Document抽象文档用动态属性驾驭树形数据结构深入解析 Java 设计模式之 Abstract Document抽象文档用动态属性驾驭树形数据结构 Abstract Document抽象文档是 j示例工程教程上一篇三步免费解锁原神144帧Genshin FPS Unlocker完整使用指南下一篇CANN Runtime Profiling 错误码 EK0204Resource_Error排查指南磁盘空间不足的成因与处理创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考