写在前面
Hello,我是易元,这篇文章是我学习设计模式时的笔记和心得体会。如果其中有错误,欢迎大家留言指正!
核心思想
享元模式的核心在于分离对象的内在状态(共享)和外在状态(独有)。通过共享不变部分,减少系统中重复对象的数量,从而提高系统性能。其关键点如下:
- 分离不变与易变
将对象中不变的信息(如商品的基础信息)与易变的信息(如商品的颜色、数量)分离,不变部分作为共享资源,易变部分作为外部状态传入。 - 对象复用
通过工厂机制确保相同元数据的对象唯一,避免重复创建相似对象。 - 集中管理
将共享对象的逻辑集中管理,简化客户端代码,提高系统可维护性。
角色定义
Flyweight(抽象享元接口)
定义享元对象的公共接口,声明外部状态的操作方法。
ConcreteFlyweight(具体享元实现)
实现抽象享元接口,封装共享的内部状态。
FlyweightFactory(享元工厂)
负责创建和管理享元对象,确保相同内在状态的享元对象唯一。
应用场景分析
- 系统中存在大量相似对象
当系统中需要频繁创建大量相似对象时,享元模式可以有效减少内存占用。 - 对象可分离出不变部分和可变部分
对象的状态可以明确划分为内部状态和外部状态。 - 需要共享状态的管理能力
外部状态的变化不会影响内部状态的共享。 - 对象创建成本较高
创建对象需要消耗较多资源时,享元模式可以通过共享对象降低开销。
实践案例:购物车系统重构
原始代码问题分析
购物车类
arduino
@Data
public class CartItem {
/**
* 商品id
*/
private String productId;
/**
* 商品名称
*/
private String name;
/**
* 商品描述
*/
private String description;
/**
* 商品图
*/
private String imageUrl;
/**
* 颜色
*/
private String color;
/**
* 数量
*/
private int quantity;
/**
* 价格
*/
private double price;
/**
* 库存
*/
private int stock;
/**
* 是否促销折扣
*/
private boolean isOnSale;
/**
* 计算商品小计
*
* @return 商品价格
*/
public double calculateSubtotal() {
return price * quantity;
}
/**
* 购买限制校验
*/
public void validateSpec() {
if (color.equals("荧光色") && quantity > 2) {
System.out.println("特殊颜色限购2件");
}
}
/**
* 库存校验
*
* @return boolean
*/
public boolean checkStock() {
return stock >= quantity;
}
/**
* 获取商品实际价格
*
* @return 价格
*/
public double getDiscountedPrice() {
return isOnSale ? price * 0.8 : price;
}
}
原始购物车类CartItem
存在以下问题:
- 商品基础信息重复存储
每个购物车项都存储完整的商品信息,导致内存浪费。 - 价格计算逻辑分散
价格计算逻辑分布在不同的方法中,维护困难。 - 规格校验缺乏统一管理
校验逻辑散落在各个方法中,不符合单一职责原则。
重构思路
- 分离商品元数据(享元对象)
创建ProductMetadata
类,封装不变的商品基础信息。 - 购物车项仅保留可变状态
CartItem
类仅保留颜色、数量和促销标志等可变状态。 - 创建享元工厂管理商品数据
ProductFactory
类负责创建和管理ProductMetadata
对象,确保相同商品的唯一性。
重构后的代码
商品元数据-享元对象
arduino
@Setter
@Getter
public class ProductMetadata {
private final String productId;
private final String name;
private final String description;
private final String imageUrl;
private final double basePrice;
private final int totalStock;
public ProductMetadata(String productId, String name, String description, String imageUrl, double basePrice, int totalStock) {
this.productId = productId;
this.name = name;
this.description = description;
this.imageUrl = imageUrl;
this.basePrice = basePrice;
this.totalStock = totalStock;
}
public void validatePurchase(String color, int quantity) {
if ("荧光色".equals(color) && quantity > 2) {
System.out.println("特殊颜色限购2件");
}
}
public double calculatePrice(int quantity, boolean isOnSale) {
double finalPrice = isOnSale ? basePrice * 0.8 : basePrice;
return finalPrice * quantity;
}
}
享元对象-外部状态维护
arduino
@Getter
@Setter
public class CartItem {
private ProductMetadata product;
private String color;
private int quantity;
private boolean isOnSale;
public CartItem(ProductMetadata product, String color, int quantity, boolean isOnSale) {
this.product = product;
this.color = color;
this.quantity = quantity;
this.isOnSale = isOnSale;
}
public double calculateSubtotal() {
return product.calculatePrice(quantity, isOnSale);
}
public void validate() {
product.validatePurchase(color, quantity);
}
}
享元工厂类
arduino
public class ProductFactory {
private static final Map<String, ProductMetadata> products = new HashMap<>();
public static ProductMetadata getProduct(String productId, String name, String description, String imageUrl, double price, int stock) {
return products.computeIfAbsent(productId, p -> new ProductMetadata(productId, name, description, imageUrl, price, stock));
}
}
重构后各模块职责分离明确:
- 享元对象(ProductMetadata) :仅关注商品本身的属性和业务逻辑。
- 轻量化客户端(CartItem) :仅关注购物车项的可变状态(颜色、数量)。
- 工厂(ProductFactory) :专注对象的创建和缓存管理。
长话短说
适用场景特征
- 系统中存在大量相似对象
- 对象的大部分状态可以外部化
- 需要缓冲池优化性能
- 对象标识不重要(共享不影响业务逻辑)
使用场景判断
特征 | 是否适用享元模式 |
---|---|
存在大量相似对象 | 是 |
对象可分离内外状态 | 是 |
需要共享状态管理 | 是 |
对象创建成本高 | 可选 |
实施步骤
- 分离状态:识别可共享的内部状态和必须保留的外部状态
- 创建享元接口:定义操作外部状态的方法
- 实现具体享元:封装内部状态的处理逻辑
- 构建工厂:控制实例创建,保证共享
- 改造客户端:将原本独立的对象拆分为享元+外部状态
最佳实践
• 结合对象池技术提升性能
• 使用弱引用管理享元池(防内存泄漏)
• 考虑线程安全性(特别是享元工厂)
• 监控享元命中率优化共享策略