设计模式-享元模式

写在前面

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. 改造客户端:将原本独立的对象拆分为享元+外部状态

最佳实践

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

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

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

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

相关推荐
雷渊1 分钟前
深入分析Spring的事务隔离级别及实现原理
java·后端·面试
Smilejudy11 分钟前
不可或缺的相邻引用
后端
惜鸟11 分钟前
Elasticsearch 的字段类型总结
后端
rebel13 分钟前
Java获取excel附件并解析解决方案
java·后端
微客鸟窝15 分钟前
Redis常用数据类型和命令
后端
熊猫片沃子17 分钟前
centos挂载数据盘
后端·centos
微客鸟窝18 分钟前
Redis配置文件解读
后端
不靠谱程序员20 分钟前
"白描APP" OCR 软件 API 逆向抓取
后端·爬虫
小华同学ai21 分钟前
6.4K star!企业级流程引擎黑马,低代码开发竟能如此高效!
后端·github
Paladin_z25 分钟前
【导入导出】功能设计方案(Java版)
后端