Java 23 种设计模式:从踩坑到精通 | 番外:享元模式 —— 物流包装材料管理实战 Java 23 种设计模式从踩坑到精通 | 番外享元模式 —— 物流包装材料管理实战摘要享元模式通过共享不可变对象来支持大量细粒度对象的复用将内存占用从“数量级”降到“种类级”。本文结合**智能物流中“包装材料管理”**的场景完整展示如何用享元工厂缓存纸箱、泡沫箱、木箱等包装材料让大量订单共享同一批材料实例并结合对象池与单例模式深度对比帮你掌握“共享细粒度对象”的设计精髓。️本文阅读地图3 分钟速览为什么每个订单创建一个包装材料对象会撑爆内存✅ 享元模式核心内部状态共享 外部状态传入手写包装材料工厂纸箱、泡沫箱、木箱任意复用 享元 vs 对象池不可变共享 vs 可变复用 面试必问“享元模式和单例模式有什么区别”《Java 23 种设计模式从踩坑到精通》开篇系列介绍与目录 正篇享元模式 Flyweight —— 内存吃不消试试共享细粒度对象 当前番外 · 享元模式 × 物流包装材料管理 返回系列总目录1. 物流包装材料的对象爆炸问题在物流仓库中每天要处理成千上万个订单每个订单都需要选择合适的包装材料——纸箱、泡沫箱、木箱等。如果为每个订单都new一个包装材料对象系统中会瞬间产生数万个对象导致内存飙升和 GC 压力增大。但实际上包装材料的规格是固定的、不可变的如“小号纸箱30×20×15cm承重 5kg”变化的只是每个订单的打包参数重量、目的地。享元模式正是为这类场景而生将“固定规格”作为内部状态缓存共享将“变化参数”作为外部状态由客户端传入。1.1 你的场景该不该用享元判断标准是 → 用享元否 → 用其他方式系统中有大量相似对象内存成为瓶颈✅❌对象的大部分状态可提取为不可变的共享部分✅❌共享对象不需要频繁修改✅❌每个对象状态完全独立无法提取共享部分❌直接 new 或考虑对象池2. 享元模式 UML物流包装材料场景3. 完整源码实现3.1 抽象享元包装材料接口 (PackagingMaterial)/** * 抽象享元包装材料接口 * 内部状态intrinsic材料类型、尺寸、承重共享、不可变 * 外部状态extrinsic订单ID、重量、目的地每次调用传入 */publicinterfacePackagingMaterial{voidpack(StringorderId,doubleweight,Stringdestination);}白话所有包装材料都能“打包”但打包时需要的订单信息重量、目的地是外部传进来的不是材料自己的属性——这就是外部状态。3.2 具体享元纸箱 (CartonBox)/** * 具体享元纸箱 * 内部状态是不可变的可以被多个订单共享 */publicclassCartonBoximplementsPackagingMaterial{// 内部状态intrinsic state- 共享且不可变privatefinalStringboxType;privatefinalStringdimensions;privatefinaldoublemaxWeight;publicCartonBox(StringboxType,Stringdimensions,doublemaxWeight){this.boxTypeboxType;this.dimensionsdimensions;this.maxWeightmaxWeight;System.out.printf( 创建纸箱[%s] | 尺寸%s | 承重%.1fkg\n,boxType,dimensions,maxWeight);}Overridepublicvoidpack(StringorderId,doubleweight,Stringdestination){if(weightmaxWeight){System.out.printf( ⚠️ 订单[%s]重量%.1fkg超出纸箱[%s]承重限制\n,orderId,weight,boxType);return;}System.out.printf( ✅ 订单[%s]使用%s纸箱打包 → 重量%.1fkg → 发往%s\n,orderId,boxType,weight,destination);}publicStringgetBoxType(){returnboxType;}publicStringgetDimensions(){returndimensions;}publicdoublegetMaxWeight(){returnmaxWeight;}}白话纸箱的箱型、尺寸、承重是“内部状态”创建后不能改。pack()方法接收的订单ID、重量、目的地是“外部状态”每次打包时传进来不存到对象里。3.3 具体享元泡沫箱 (FoamBox) 与 木箱 (WoodenCrate)// 泡沫箱用于冷链运输publicclassFoamBoximplementsPackagingMaterial{privatefinalStringboxType;privatefinalStringdimensions;privatefinaldoublemaxWeight;publicFoamBox(StringboxType,Stringdimensions,doublemaxWeight){this.boxTypeboxType;this.dimensionsdimensions;this.maxWeightmaxWeight;System.out.printf( ❄️ 创建泡沫箱[%s] | 尺寸%s | 承重%.1fkg\n,boxType,dimensions,maxWeight);}Overridepublicvoidpack(StringorderId,doubleweight,Stringdestination){if(weightmaxWeight){System.out.printf( ⚠️ 订单[%s]重量%.1fkg超出泡沫箱[%s]承重限制\n,orderId,weight,boxType);return;}System.out.printf( ✅ 订单[%s]使用%s泡沫箱冷链打包 → 重量%.1fkg → 发往%s\n,orderId,boxType,weight,destination);}}// 木箱用于重型设备运输publicclassWoodenCrateimplementsPackagingMaterial{privatefinalStringboxType;privatefinalStringdimensions;privatefinaldoublemaxWeight;publicWoodenCrate(StringboxType,Stringdimensions,doublemaxWeight){this.boxTypeboxType;this.dimensionsdimensions;this.maxWeightmaxWeight;System.out.printf( 创建木箱[%s] | 尺寸%s | 承重%.1fkg\n,boxType,dimensions,maxWeight);}Overridepublicvoidpack(StringorderId,doubleweight,Stringdestination){if(weightmaxWeight){System.out.printf( ⚠️ 订单[%s]重量%.1fkg超出木箱[%s]承重限制\n,orderId,weight,boxType);return;}System.out.printf( ✅ 订单[%s]使用%s木箱打包 → 重量%.1fkg → 发往%s\n,orderId,boxType,weight,destination);}}3.4 非共享具体享元定制包装 (CustomPackaging)/** * 非共享具体享元定制包装 * 每个订单的定制需求不同不放入享元池共享 */publicclassCustomPackagingimplementsPackagingMaterial{privatefinalStringorderId;privatefinalStringcustomSpecs;publicCustomPackaging(StringorderId,StringcustomSpecs){this.orderIdorderId;this.customSpecscustomSpecs;System.out.printf( 创建定制包装 | 订单[%s] | 规格%s\n,orderId,customSpecs);}Overridepublicvoidpack(StringorderId,doubleweight,Stringdestination){System.out.printf( ✅ 订单[%s]使用定制包装[%s]打包 → 重量%.1fkg → 发往%s\n,orderId,customSpecs,weight,destination);}}白话不是所有东西都能共享。定制包装每个订单都不一样所以不放进享元池每次单独创建。3.5 享元工厂包装材料工厂 (PackagingMaterialFactory)importjava.util.Map;importjava.util.concurrent.ConcurrentHashMap;/** * 享元工厂包装材料工厂线程安全 */publicclassPackagingMaterialFactory{privatefinalMapString,PackagingMaterialpoolnewConcurrentHashMap();privateinttotalCreated0;publicPackagingMaterialgetMaterial(StringboxType){returnpool.computeIfAbsent(boxType,this::createMaterial);}privatePackagingMaterialcreateMaterial(StringboxType){totalCreated;switch(boxType){caseSMALL_CARTON:returnnewCartonBox(小号纸箱,30x20x15cm,5.0);caseMEDIUM_CARTON:returnnewCartonBox(中号纸箱,50x40x30cm,15.0);caseLARGE_CARTON:returnnewCartonBox(大号纸箱,80x60x40cm,30.0);caseFOAM_SMALL:returnnewFoamBox(小号泡沫箱,40x30x20cm,10.0);caseFOAM_LARGE:returnnewFoamBox(大号泡沫箱,60x40x30cm,20.0);caseWOODEN_CRATE:returnnewWoodenCrate(重型木箱,120x80x60cm,200.0);default:thrownewIllegalArgumentException(未知的包装材料类型boxType);}}publicintgetTotalCreated(){returntotalCreated;}publicintgetPoolSize(){returnpool.size();}}白话享元工厂就像一个“材料仓库”——你要什么材料它先去仓库里找pool有就直接给你没有再创建。computeIfAbsent保证了多线程安全。3.6 客户端测试publicclassClient{publicstaticvoidmain(String[]args){System.out.println( 智能物流包装材料管理系统享元模式\n);PackagingMaterialFactoryfactorynewPackagingMaterialFactory();// 场景110个订单大量复用相同材料System.out.println(【场景1】处理一批电商订单);String[]orderTypes{SMALL_CARTON,MEDIUM_CARTON,SMALL_CARTON,FOAM_SMALL,LARGE_CARTON,SMALL_CARTON,MEDIUM_CARTON,FOAM_SMALL,WOODEN_CRATE,SMALL_CARTON};for(inti0;iorderTypes.length;i){PackagingMaterialmaterialfactory.getMaterial(orderTypes[i]);material.pack(String.format(ORD%03d,i1),orderTypes[i].contains(WOODEN)?150.0:3.0,上海仓库);}// 场景2定制包装非共享System.out.println(\n【场景2】定制订单);CustomPackagingcustomnewCustomPackaging(ORD101,防震防水定制尺寸);custom.pack(ORD101,8.5,北京仓库);// 场景3多线程并发System.out.println(\n【场景3】并发测试);Runnabletask()-{PackagingMaterialmaterialfactory.getMaterial(MEDIUM_CARTON);material.pack(Thread.currentThread().getName(),10.0,广州仓库);};for(inti0;i5;i){newThread(task,线程-(i1)).start();}// 统计System.out.println(\n 统计信息 );System.out.printf(总共创建享元对象%d 个 | 池大小%d 种\n,factory.getTotalCreated(),factory.getPoolSize());System.out.println(10个订单 5个并发仅创建了6种材料对象大幅节省内存);}}4. 运行结果 智能物流包装材料管理系统享元模式 【场景1】处理一批电商订单 创建纸箱[小号纸箱] | 尺寸30x20x15cm | 承重5.0kg ✅ 订单[ORD001]使用小号纸箱打包 → 重量3.0kg → 发往上海仓库 创建纸箱[中号纸箱] | 尺寸50x40x30cm | 承重15.0kg ✅ 订单[ORD002]使用中号纸箱打包 → 重量3.0kg → 发往上海仓库 ✅ 订单[ORD003]使用小号纸箱打包 → 重量3.0kg → 发往上海仓库 ← 复用 ❄️ 创建泡沫箱[小号泡沫箱] | 尺寸40x30x20cm | 承重10.0kg ✅ 订单[ORD004]使用小号泡沫箱冷链打包 → 重量3.0kg → 发往上海仓库 创建纸箱[大号纸箱] | 尺寸80x60x40cm | 承重30.0kg ✅ 订单[ORD005]使用大号纸箱打包 → 重量25.0kg → 发往上海仓库 ✅ 订单[ORD006]使用小号纸箱打包 → 重量3.0kg → 发往上海仓库 ← 复用 ✅ 订单[ORD007]使用中号纸箱打包 → 重量3.0kg → 发往上海仓库 ← 复用 ✅ 订单[ORD008]使用小号泡沫箱冷链打包 → 重量3.0kg → 发往上海仓库 ← 复用 创建木箱[重型木箱] | 尺寸120x80x60cm | 承重200.0kg ✅ 订单[ORD009]使用重型木箱打包 → 重量150.0kg → 发往上海仓库 ✅ 订单[ORD010]使用小号纸箱打包 → 重量3.0kg → 发往上海仓库 ← 复用 【场景2】定制订单 创建定制包装 | 订单[ORD101] | 规格防震防水定制尺寸 ✅ 订单[ORD101]使用定制包装打包 → 重量8.5kg → 发往北京仓库 【场景3】并发测试 ✅ 订单[线程-1]使用中号纸箱打包 → 重量10.0kg → 发往广州仓库 ✅ 订单[线程-2]使用中号纸箱打包 → 重量10.0kg → 发往广州仓库 ... 统计信息 总共创建享元对象6 个 | 池大小6 种 10个订单 5个并发仅创建了6种材料对象大幅节省内存5. 核心角色回顾角色职责对应代码Flyweight定义享元对象的公共接口PackagingMaterialConcreteFlyweight内部状态不可变可共享CartonBox/FoamBox/WoodenCrateUnsharedConcreteFlyweight不共享的具体享元CustomPackagingFlyweightFactory维护享元池复用已有实例PackagingMaterialFactory6. 享元模式 vs 对象池 vs 单例对比项享元模式对象池单例模式实例数量每种内部状态一个共享实例N 个池中多个实例借出归还全局唯一1 个对象可变性不可变核心约束可变用完归还通常持有可变全局状态典型应用Integer.valueOf()、包装材料数据库连接池、线程池Spring 单例 Bean、Runtime一句话记忆享元是“按种类共享不可变对象”对象池是“借还复用可变对象”单例是“全类唯一实例”。包装材料共享是享元连接池是对象池配置中心是单例。7. 享元模式的优缺点优点缺点大幅减少内存占用代码复杂度增加需区分内外部状态减少对象创建和 GC 压力享元对象必须不可变限制了灵活性线程安全工厂用ConcurrentHashMap外部状态管理成本高8. 六大设计原则体现原则体现单一职责享元对象只负责内部状态外部状态由客户端管理开闭原则新增包装材料类型只需扩展工厂里氏替换所有包装材料可替换PackagingMaterial接口依赖倒置客户端依赖抽象PackagingMaterial接口隔离PackagingMaterial接口只有pack()一个方法迪米特法则客户端只与工厂交互不知池化细节附 享元模式 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 skinparam interface { BackgroundColor #E8F5E9 BorderColor #2E7D32 } 2. 抽象享元 interface PackagingMaterial { pack(orderId, weight, destination) } note right of PackagingMaterial b抽象享元包装材料/b -- 定义享元对象的公共接口 extrinsicState外部状态作为方法参数传入 end note 3. 具体享元 class CartonBox implements PackagingMaterial { - boxType : String - dimensions : String - maxWeight : double pack(orderId, weight, destination) } note right of CartonBox b具体享元纸箱/b -- intrinsicState内部状态 箱型、尺寸、承重 这些是共享的、不可变的 end note class FoamBox implements PackagingMaterial { - boxType : String - dimensions : String - maxWeight : double pack(orderId, weight, destination) } note right of FoamBox b具体享元泡沫箱/b -- 用于冷链运输 intrinsicState箱型、尺寸、承重 end note class WoodenCrate implements PackagingMaterial { - boxType : String - dimensions : String - maxWeight : double pack(orderId, weight, destination) } note right of WoodenCrate b具体享元木箱/b -- 用于重型设备运输 intrinsicState箱型、尺寸、承重 end note 4. 享元工厂 class PackagingMaterialFactory { - pool : MapString, PackagingMaterial getMaterial(boxType) : PackagingMaterial getTotalCreated() : int } note right of PackagingMaterialFactory b享元工厂线程安全/b -- 维护享元对象池 确保相同类型的包装材料只创建一次 使用 ConcurrentHashMap 保证线程安全 end note 5. 非共享具体享元 class CustomPackaging implements PackagingMaterial { - orderId : String - customSpecs : String pack(orderId, weight, destination) } note bottom of CustomPackaging b非共享具体享元/b -- 特殊定制包装不共享 每个订单独立创建 end note 6. 关系连线 PackagingMaterial |.. CartonBox : 实现 PackagingMaterial |.. FoamBox : 实现 PackagingMaterial |.. WoodenCrate : 实现 PackagingMaterial |.. CustomPackaging : 实现 PackagingMaterialFactory .. CartonBox : 创建/管理 PackagingMaterialFactory .. FoamBox : 创建/管理 PackagingMaterialFactory .. WoodenCrate : 创建/管理 enduml 《Java 23 种设计模式从踩坑到精通》快速导航开篇系列介绍与目录正篇享元模式 Flyweight —— 内存吃不消试试共享细粒度对象当前番外 · 享元模式 × 物流包装材料管理你在这里创建型模式汇总结构型模式汇总行为型模式汇总 关注《Java 23 种设计模式从踩坑到精通》用 25 篇文章彻底吃透设计模式。福利预告全系列代码及 UML 源码将在完结时统一打包开放点击「关注」「收藏」第一时间获取。 除了设计模式我也在深挖智能物流实战WMS、托盘调度、机器学习落地。欢迎点击头像看看专栏 《出版社物流WMS智能调度实战》、《电商多平台电子面单对接实战》。技术相通思路可鉴。