Java 23 种设计模式:从踩坑到精通 | 番外:享元模式 —— 物流包装材料管理实战

📅 2026/7/29 22:19:28 👁️ 阅读次数 📝 编程学习
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.boxType=boxType;this.dimensions=dimensions;this.maxWeight=maxWeight;System.out.printf(" 📦 创建纸箱[%s] | 尺寸:%s | 承重:%.1fkg\n",boxType,dimensions,maxWeight);}@Overridepublicvoidpack(StringorderId,doubleweight,Stringdestination){if(weight>maxWeight){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.boxType=boxType;this.dimensions=dimensions;this.maxWeight=maxWeight;System.out.printf(" ❄️ 创建泡沫箱[%s] | 尺寸:%s | 承重:%.1fkg\n",boxType,dimensions,maxWeight);}@Overridepublicvoidpack(StringorderId,doubleweight,Stringdestination){if(weight>maxWeight){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.boxType=boxType;this.dimensions=dimensions;this.maxWeight=maxWeight;System.out.printf(" 🪵 创建木箱[%s] | 尺寸:%s | 承重:%.1fkg\n",boxType,dimensions,maxWeight);}@Overridepublicvoidpack(StringorderId,doubleweight,Stringdestination){if(weight>maxWeight){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.orderId=orderId;this.customSpecs=customSpecs;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{privatefinalMap<String,PackagingMaterial>pool=newConcurrentHashMap<>();privateinttotalCreated=0;publicPackagingMaterialgetMaterial(StringboxType){returnpool.computeIfAbsent(boxType,this::createMaterial);}privatePackagingMaterialcreateMaterial(StringboxType){totalCreated++;switch(boxType){case"SMALL_CARTON":returnnewCartonBox("小号纸箱","30x20x15cm",5.0);case"MEDIUM_CARTON":returnnewCartonBox("中号纸箱","50x40x30cm",15.0);case"LARGE_CARTON":returnnewCartonBox("大号纸箱","80x60x40cm",30.0);case"FOAM_SMALL":returnnewFoamBox("小号泡沫箱","40x30x20cm",10.0);case"FOAM_LARGE":returnnewFoamBox("大号泡沫箱","60x40x30cm",20.0);case"WOODEN_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");PackagingMaterialFactoryfactory=newPackagingMaterialFactory();// 场景1:10个订单大量复用相同材料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(inti=0;i<orderTypes.length;i++){PackagingMaterialmaterial=factory.getMaterial(orderTypes[i]);material.pack(String.format("ORD%03d",i+1),orderTypes[i].contains("WOODEN")?150.0:3.0,"上海仓库");}// 场景2:定制包装(非共享)System.out.println("\n【场景2】定制订单");CustomPackagingcustom=newCustomPackaging("ORD101","防震+防水+定制尺寸");custom.pack("ORD101",8.5,"北京仓库");// 场景3:多线程并发System.out.println("\n【场景3】并发测试");Runnabletask=()->{PackagingMaterialmaterial=factory.getMaterial("MEDIUM_CARTON");material.pack(Thread.currentThread().getName(),10.0,"广州仓库");};for(inti=0;i<5;i++){newThread(task,"线程-"+(i+1)).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定义享元对象的公共接口PackagingMaterial
ConcreteFlyweight内部状态不可变,可共享CartonBox/FoamBox/WoodenCrate
UnsharedConcreteFlyweight不共享的具体享元CustomPackaging
FlyweightFactory维护享元池,复用已有实例PackagingMaterialFactory

6. 享元模式 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 : Map<String, 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智能调度实战》、《电商多平台电子面单对接实战》。技术相通,思路可鉴。