Java 23 种设计模式:从踩坑到精通 | 番外:组合模式 —— 仓储层级管理实战

发布时间:2026/7/26 23:54:25
Java 23 种设计模式:从踩坑到精通 | 番外:组合模式 —— 仓储层级管理实战
Java 23 种设计模式从踩坑到精通 | 番外组合模式 —— 仓储层级管理实战摘要组合模式将对象组织成树形结构让客户端可以统一处理单个对象叶子和组合对象容器完美应对“部分-整体”的层级关系。本文结合智能物流中“仓库-区域-货架”的层级管理体系完整展示如何用组合模式统一操作仓库、区域和货架递归聚合容量与使用率并对比透明方式与安全方式的优劣帮你掌握“树形结构的一致性处理”精髓。️本文阅读地图3 分钟速览为什么仓库管理要统一处理货架和区域✅ 组合模式核心角色抽象构件、叶子、容器手写仓储层级树物流中心 → 区域 → 货架 递归聚合容量与使用率一键展示全局视图 面试必问“组合模式和装饰器模式有什么区别”《Java 23 种设计模式从踩坑到精通》开篇系列介绍与目录 正篇组合模式 Composite —— 树形结构处理部分与整体一视同仁 当前番外 · 组合模式 × 仓储层级管理 返回系列总目录1. 仓储层级管理的痛点在智能仓储系统中物流中心下辖多个存储区域每个区域包含多个货架。如果需要对整个仓库进行容量统计、空间查询或结构展示传统方式可能需要针对货架和区域分别编写遍历逻辑代码臃肿且难以扩展。更糟糕的是新增一种节点类型如“分拣区”时所有相关代码都要修改。组合模式的解决思路将货架叶子节点和区域/仓库容器节点统一抽象为WarehouseNode客户端无需区分当前操作的是单个货架还是整片区域递归调用即可完成全局统计与展示。新增节点类型只需继承抽象构件完全符合开闭原则。1.1 你的场景该不该用组合模式判断标准是 → 用组合模式否 → 用其他方式对象之间有明显“整体-部分”层级关系✅❌希望统一处理单个对象和组合对象✅❌需要递归聚合数据如总量、使用率✅❌结构扁平无层级嵌套❌直接集合遍历即可2. 组合模式 UML仓储层级管理场景3. 完整源码实现3.1 抽象构件仓库节点 (WarehouseNode)/** * 抽象构件仓库节点 * 定义仓库树中所有节点的统一接口 */publicabstractclassWarehouseNode{protectedStringname;publicWarehouseNode(Stringname){this.namename;}publicStringgetName(){returnname;}publicabstractintgetCapacity();publicabstractintgetUsedSpace();publicintgetAvailableSpace(){returngetCapacity()-getUsedSpace();}publicabstractvoiddisplayInfo(intdepth);// 子节点管理方法默认抛异常透明组合方式publicvoidadd(WarehouseNodenode){thrownewUnsupportedOperationException(不支持添加子节点);}publicvoidremove(WarehouseNodenode){thrownewUnsupportedOperationException(不支持移除子节点);}publicWarehouseNodegetChild(intindex){thrownewUnsupportedOperationException(不支持获取子节点);}protectedStringgetIndent(intdepth){return .repeat(depth);}}白话WarehouseNode就像仓库里的“任何东西”——货架、区域、甚至整个物流中心都有容量、已用空间都能展示信息。容器会实现增删子节点而叶子货架则会抛异常告诉调用者“我没有子节点”。3.2 叶子构件货架 (Shelf)/** * 叶子构件货架 * 仓库的最末端存储单元没有子节点 */publicclassShelfextendsWarehouseNode{privateintcapacity;// 总容量立方米privateintusedSpace;// 已用空间立方米publicShelf(Stringname,intcapacity){super(name);this.capacitycapacity;this.usedSpace0;}OverridepublicintgetCapacity(){returncapacity;}OverridepublicintgetUsedSpace(){returnusedSpace;}// 出入库操作publicbooleanstoreGoods(intvolume){if(usedSpacevolumecapacity){usedSpacevolume;System.out.printf(✅ 货架[%s] 存入 %.1f m³ 货物\n,name,(double)volume);returntrue;}System.out.printf(❌ 货架[%s] 空间不足剩余 %.1f m³请求 %.1f m³\n,name,(double)getAvailableSpace(),(double)volume);returnfalse;}publicbooleanretrieveGoods(intvolume){if(usedSpacevolume){usedSpace-volume;System.out.printf( 货架[%s] 取出 %.1f m³ 货物\n,name,(double)volume);returntrue;}System.out.printf(❌ 货架[%s] 货物不足当前 %.1f m³请求 %.1f m³\n,name,(double)usedSpace,(double)volume);returnfalse;}OverridepublicvoiddisplayInfo(intdepth){StringindentgetIndent(depth);doubleusageRatecapacity0?(double)usedSpace/capacity*100:0;System.out.printf(%s 货架[%s] | 容量: %d m³ | 已用: %d m³ | 可用: %d m³ | 使用率: %.1f%%\n,indent,name,capacity,usedSpace,getAvailableSpace(),usageRate);}}白话货架是树的叶子它直接管理自己的库存增删子节点的方法会直接报错。但它能存入、取出货物并实时返回已用空间。3.3 容器构件仓库复合节点 (WarehouseComposite)importjava.util.*;/** * 容器构件仓库/区域 * 可以包含子节点区域或货架递归聚合子节点信息 */publicclassWarehouseCompositeextendsWarehouseNode{privateListWarehouseNodechildrennewArrayList();publicWarehouseComposite(Stringname){super(name);}OverridepublicintgetCapacity(){returnchildren.stream().mapToInt(WarehouseNode::getCapacity).sum();}OverridepublicintgetUsedSpace(){returnchildren.stream().mapToInt(WarehouseNode::getUsedSpace).sum();}OverridepublicvoiddisplayInfo(intdepth){StringindentgetIndent(depth);doubleusageRategetCapacity()0?(double)getUsedSpace()/getCapacity()*100:0;System.out.printf(%s %s | 总容量: %d m³ | 已用: %d m³ | 可用: %d m³ | 使用率: %.1f%%\n,indent,name,getCapacity(),getUsedSpace(),getAvailableSpace(),usageRate);for(WarehouseNodechild:children){child.displayInfo(depth1);// 递归展示}}Overridepublicvoidadd(WarehouseNodenode){children.add(node);System.out.printf(➕ 添加节点 [%s] 到 [%s]\n,node.getName(),name);}Overridepublicvoidremove(WarehouseNodenode){if(children.remove(node)){System.out.printf(➖ 从 [%s] 移除节点 [%s]\n,name,node.getName());}}OverridepublicWarehouseNodegetChild(intindex){if(index0indexchildren.size()){returnchildren.get(index);}returnnull;}publicintgetChildCount(){returnchildren.size();}publicListWarehouseNodegetChildren(){returnnewArrayList(children);}}白话区域/仓库是容器节点它本身不存储货物而是聚合所有子节点的容量和已用空间。调用displayInfo时它会递归展示整棵子树客户端无需区分货架或区域一次调用即可看到全局视图。3.4 客户端测试publicclassClient{publicstaticvoidmain(String[]args){System.out.println( 智能仓储管理系统组合模式\n);// 构建仓库树形结构WarehouseCompositelogisticsCenternewWarehouseComposite(华东物流中心);WarehouseComposite常温区newWarehouseComposite(A区-常温存储区);常温区.add(newShelf(A-01货架,100));常温区.add(newShelf(A-02货架,100));常温区.add(newShelf(A-03货架,100));WarehouseComposite冷藏区newWarehouseComposite(B区-冷藏存储区);冷藏区.add(newShelf(B-01货架,80));冷藏区.add(newShelf(B-02货架,80));WarehouseComposite危险品区newWarehouseComposite(C区-危险品存储区);危险品区.add(newShelf(C-01货架,50));危险品区.add(newShelf(C-02货架,50));危险品区.add(newShelf(C-03货架,50));危险品区.add(newShelf(C-04货架,50));logisticsCenter.add(常温区);logisticsCenter.add(冷藏区);logisticsCenter.add(危险品区);// 场景1展示完整仓库结构System.out.println(【场景1】展示完整仓库结构);logisticsCenter.displayInfo(0);// 场景2对单个货架操作System.out.println(\n【场景2】对单个货架操作);ShelfshelfA01(Shelf)常温区.getChild(0);shelfA01.storeGoods(60);shelfA01.storeGoods(30);shelfA01.retrieveGoods(20);// 场景3对区域批量操作System.out.println(\n【场景3】对区域批量存入);ShelfshelfA02(Shelf)常温区.getChild(1);shelfA02.storeGoods(80);// 场景4操作后再次展示System.out.println(\n【场景4】操作后再次展示仓库结构);logisticsCenter.displayInfo(0);// 场景5统计报表System.out.println(\n【场景5】仓储统计报表);System.out.printf( 仓库总览\n);System.out.printf( 总容量: %d m³\n,logisticsCenter.getCapacity());System.out.printf( 已用空间: %d m³\n,logisticsCenter.getUsedSpace());System.out.printf( 可用空间: %d m³\n,logisticsCenter.getAvailableSpace());System.out.printf( 整体使用率: %.1f%%\n,(double)logisticsCenter.getUsedSpace()/logisticsCenter.getCapacity()*100);System.out.printf( 子区域数量: %d 个\n,logisticsCenter.getChildCount());}}4. 运行结果 智能仓储管理系统组合模式 ➕ 添加节点 [A区-常温存储区] 到 [华东物流中心] ➕ 添加节点 [A-01货架] 到 [A区-常温存储区] ➕ 添加节点 [A-02货架] 到 [A区-常温存储区] ➕ 添加节点 [A-03货架] 到 [A区-常温存储区] ➕ 添加节点 [B区-冷藏存储区] 到 [华东物流中心] ➕ 添加节点 [B-01货架] 到 [B区-冷藏存储区] ➕ 添加节点 [B-02货架] 到 [B区-冷藏存储区] ➕ 添加节点 [C区-危险品存储区] 到 [华东物流中心] ➕ 添加节点 [C-01货架] 到 [C区-危险品存储区] ➕ 添加节点 [C-02货架] 到 [C区-危险品存储区] ➕ 添加节点 [C-03货架] 到 [C区-危险品存储区] ➕ 添加节点 [C-04货架] 到 [C区-危险品存储区] 【场景1】展示完整仓库结构 华东物流中心 | 总容量: 640 m³ | 已用: 0 m³ | 可用: 640 m³ | 使用率: 0.0% A区-常温存储区 | 总容量: 300 m³ | 已用: 0 m³ | 可用: 300 m³ | 使用率: 0.0% 货架[A-01货架] | 容量: 100 m³ | 已用: 0 m³ | 可用: 100 m³ | 使用率: 0.0% 货架[A-02货架] | 容量: 100 m³ | 已用: 0 m³ | 可用: 100 m³ | 使用率: 0.0% 货架[A-03货架] | 容量: 100 m³ | 已用: 0 m³ | 可用: 100 m³ | 使用率: 0.0% B区-冷藏存储区 | 总容量: 160 m³ | 已用: 0 m³ | 可用: 160 m³ | 使用率: 0.0% 货架[B-01货架] | 容量: 80 m³ | 已用: 0 m³ | 可用: 80 m³ | 使用率: 0.0% 货架[B-02货架] | 容量: 80 m³ | 已用: 0 m³ | 可用: 80 m³ | 使用率: 0.0% C区-危险品存储区 | 总容量: 200 m³ | 已用: 0 m³ | 可用: 200 m³ | 使用率: 0.0% 货架[C-01货架] | 容量: 50 m³ | 已用: 0 m³ | 可用: 50 m³ | 使用率: 0.0% 货架[C-02货架] | 容量: 50 m³ | 已用: 0 m³ | 可用: 50 m³ | 使用率: 0.0% 货架[C-03货架] | 容量: 50 m³ | 已用: 0 m³ | 可用: 50 m³ | 使用率: 0.0% 货架[C-04货架] | 容量: 50 m³ | 已用: 0 m³ | 可用: 50 m³ | 使用率: 0.0% 【场景2】对单个货架操作 ✅ 货架[A-01货架] 存入 60 m³ 货物 ✅ 货架[A-01货架] 存入 30 m³ 货物 货架[A-01货架] 取出 20 m³ 货物 【场景3】对区域批量存入 ✅ 货架[A-02货架] 存入 80 m³ 货物 【场景4】操作后再次展示仓库结构 华东物流中心 | 总容量: 640 m³ | 已用: 150 m³ | 可用: 490 m³ | 使用率: 23.4% A区-常温存储区 | 总容量: 300 m³ | 已用: 150 m³ | 可用: 150 m³ | 使用率: 50.0% 货架[A-01货架] | 容量: 100 m³ | 已用: 70 m³ | 可用: 30 m³ | 使用率: 70.0% 货架[A-02货架] | 容量: 100 m³ | 已用: 80 m³ | 可用: 20 m³ | 使用率: 80.0% 货架[A-03货架] | 容量: 100 m³ | 已用: 0 m³ | 可用: 100 m³ | 使用率: 0.0% B区-冷藏存储区 | 总容量: 160 m³ | 已用: 0 m³ | 可用: 160 m³ | 使用率: 0.0% 货架[B-01货架] | 容量: 80 m³ | 已用: 0 m³ | 可用: 80 m³ | 使用率: 0.0% 货架[B-02货架] | 容量: 80 m³ | 已用: 0 m³ | 可用: 80 m³ | 使用率: 0.0% C区-危险品存储区 | 总容量: 200 m³ | 已用: 0 m³ | 可用: 200 m³ | 使用率: 0.0% 货架[C-01货架] | 容量: 50 m³ | 已用: 0 m³ | 可用: 50 m³ | 使用率: 0.0% 货架[C-02货架] | 容量: 50 m³ | 已用: 0 m³ | 可用: 50 m³ | 使用率: 0.0% 货架[C-03货架] | 容量: 50 m³ | 已用: 0 m³ | 可用: 50 m³ | 使用率: 0.0% 货架[C-04货架] | 容量: 50 m³ | 已用: 0 m³ | 可用: 50 m³ | 使用率: 0.0% 【场景5】仓储统计报表 仓库总览 总容量: 640 m³ 已用空间: 150 m³ 可用空间: 490 m³ 整体使用率: 23.4% 子区域数量: 3 个5. 核心角色回顾角色职责对应代码Component定义叶子与容器的统一接口WarehouseNodeLeaf叶子节点无子节点ShelfComposite容器节点管理子节点递归聚合WarehouseComposite6. 透明方式 vs 安全方式对比项透明方式本例安全方式抽象类定义所有方法都在抽象类中定义仅公共方法在抽象类add/remove只在 Composite 中叶子节点继承后抛UnsupportedOperationException根本不需要实现add/remove优点客户端代码统一透明性高类型安全编译期发现问题缺点运行时可能抛异常客户端需区分 Leaf 和 Composite组合模式 vs 装饰器模式两者都使用了树形递归结构但目的完全不同——组合关注“整体与部分”的层级聚合装饰器关注“动态扩展功能”的层层包装。组合是“1对多”的包含关系装饰器是“1对1”的增强关系。7. 组合模式的优缺点优点缺点统一处理叶子与容器简化客户端代码透明方式下叶子节点有无用的管理方法新增节点类型符合开闭原则树深度过大时递归可能导致性能问题递归聚合数据天然适合层级结构安全方式丧失透明性需类型判断8. 六大设计原则体现原则体现单一职责货架只管库存容器只管子节点聚合开闭原则新增节点类型如“分拣区”无需修改现有代码里氏替换容器和叶子可相互替换通过抽象基类依赖倒置客户端依赖抽象WarehouseNode接口隔离透明方式下接口稍显臃肿安全方式更符合迪米特法则客户端只与顶层节点交互不关心内部结构附 组合模式 UML源码仓储层级管理场景startuml title Java 23 种设计模式从踩坑到精通 footer 折哥 | 智能物流与Java实战 1. 全局样式配置 skinparam backgroundColor #FEFEFE skinparam shadowing false skinparam classBorderColor #333333 skinparam classFontColor #1A1A1A skinparam classFontSize 14 skinparam noteFontSize 12 skinparam noteFontColor #555555 skinparam arrowColor #555555 skinparam classBackgroundColor #F9F9F9 2. 抽象构件仓库节点 abstract class WarehouseNode { - name : String getName() : String getCapacity() : int getUsedSpace() : int getAvailableSpace() : int displayInfo(int depth) : void add(WarehouseNode) : void remove(WarehouseNode) : void getChild(int) : WarehouseNode } note right of WarehouseNode b抽象构件仓库节点/b -- 定义仓库树中所有节点的统一接口 包括容量统计、空间查询、信息展示 以及子节点的增删查操作 end note 3. 叶子构件货架 class Shelf { - capacity : int - usedSpace : int getCapacity() : int getUsedSpace() : int displayInfo(int depth) : void } note right of Shelf b叶子构件货架/b -- 仓库的最末端存储单元 没有子节点直接管理货物空间 end note 4. 容器构件仓库/区域 class WarehouseComposite { - children : ListWarehouseNode getCapacity() : int getUsedSpace() : int displayInfo(int depth) : void add(WarehouseNode) : void remove(WarehouseNode) : void getChild(int) : WarehouseNode } note right of WarehouseComposite b容器构件仓库/区域/b -- 可以包含子节点区域或货架 聚合子节点的容量和已用空间 递归遍历展示完整树形结构 end note 5. 关系连线 WarehouseNode |-- Shelf : 继承 WarehouseNode |-- WarehouseComposite : 继承 WarehouseComposite o-- WarehouseNode : 包含 enduml 《Java 23 种设计模式从踩坑到精通》快速导航开篇系列介绍与目录正篇组合模式 Composite —— 树形结构处理部分与整体一视同仁当前番外 · 组合模式 × 仓储层级管理你在这里创建型模式汇总结构型模式汇总行为型模式汇总 关注《Java 23 种设计模式从踩坑到精通》用 25 篇文章彻底吃透设计模式。福利预告全系列代码及 UML 源码将在完结时统一打包开放点击「关注」「收藏」第一时间获取。 除了设计模式我也在深挖智能物流实战WMS、托盘调度、机器学习落地。欢迎点击头像看看专栏 《出版社物流WMS智能调度实战》、《电商多平台电子面单对接实战》。技术相通思路可鉴。