设计模式-享元模式

写在前面

Hello,我是易元,这篇文章是我学习设计模式时的笔记和心得体会。如果其中有错误,欢迎大家留言指正!

核心思想

享元模式的核心在于分离对象的内在状态(共享)和外在状态(独有)。通过共享不变部分,减少系统中重复对象的数量,从而提高系统性能。其关键点如下:

  1. 分离不变与易变
    将对象中不变的信息(如商品的基础信息)与易变的信息(如商品的颜色、数量)分离,不变部分作为共享资源,易变部分作为外部状态传入。
  2. 对象复用
    通过工厂机制确保相同元数据的对象唯一,避免重复创建相似对象。
  3. 集中管理
    将共享对象的逻辑集中管理,简化客户端代码,提高系统可维护性。

角色定义

Flyweight(抽象享元接口)

定义享元对象的公共接口,声明外部状态的操作方法。

ConcreteFlyweight(具体享元实现)

实现抽象享元接口,封装共享的内部状态。

FlyweightFactory(享元工厂)

负责创建和管理享元对象,确保相同内在状态的享元对象唯一。

应用场景分析

  1. 系统中存在大量相似对象
    当系统中需要频繁创建大量相似对象时,享元模式可以有效减少内存占用。
  2. 对象可分离出不变部分和可变部分
    对象的状态可以明确划分为内部状态和外部状态。
  3. 需要共享状态的管理能力
    外部状态的变化不会影响内部状态的共享。
  4. 对象创建成本较高
    创建对象需要消耗较多资源时,享元模式可以通过共享对象降低开销。

实践案例:购物车系统重构

原始代码问题分析

购物车类
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存在以下问题:

  1. 商品基础信息重复存储
    每个购物车项都存储完整的商品信息,导致内存浪费。
  2. 价格计算逻辑分散
    价格计算逻辑分布在不同的方法中,维护困难。
  3. 规格校验缺乏统一管理
    校验逻辑散落在各个方法中,不符合单一职责原则。

重构思路

  1. 分离商品元数据(享元对象)
    创建ProductMetadata类,封装不变的商品基础信息。
  2. 购物车项仅保留可变状态
    CartItem类仅保留颜色、数量和促销标志等可变状态。
  3. 创建享元工厂管理商品数据
    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) :专注对象的创建和缓存管理。

长话短说

适用场景特征

  1. 系统中存在大量相似对象
  2. 对象的大部分状态可以外部化
  3. 需要缓冲池优化性能
  4. 对象标识不重要(共享不影响业务逻辑)
使用场景判断
特征 是否适用享元模式
存在大量相似对象
对象可分离内外状态
需要共享状态管理
对象创建成本高 可选

实施步骤

  1. 分离状态:识别可共享的内部状态和必须保留的外部状态
  2. 创建享元接口:定义操作外部状态的方法
  3. 实现具体享元:封装内部状态的处理逻辑
  4. 构建工厂:控制实例创建,保证共享
  5. 改造客户端:将原本独立的对象拆分为享元+外部状态

最佳实践

• 结合对象池技术提升性能

• 使用弱引用管理享元池(防内存泄漏)

• 考虑线程安全性(特别是享元工厂)

• 监控享元命中率优化共享策略

相关推荐
陈随易39 分钟前
长跑8年,Node.js框架Koa v3.0终发布
前端·后端·程序员
lovebugs41 分钟前
Redis的高性能奥秘:深入解析IO多路复用与单线程事件驱动模型
redis·后端·面试
bug菌44 分钟前
面十年开发候选人被反问:当类被标注为@Service后,会有什么好处?我...🫨
spring boot·后端·spring
田园Coder1 小时前
Spring之IoC控制反转
后端
bxlj1 小时前
RocketMQ消息类型
后端
Asthenia04121 小时前
从NIO到Netty:盘点那些零拷贝解决方案
后端
米开朗基杨2 小时前
Cursor 最强竞争对手来了,专治复杂大项目,免费一个月
前端·后端
Asthenia04122 小时前
anal到Elasticsearch数据一致性保障分析(基于RocketMQ)
后端
Asthenia04122 小时前
整理面试复盘:设计Elasticsearch索引与高效多级分类筛选
后端
Asthenia04122 小时前
RocketMQ延迟消息可靠性分析与补偿机制
后端