设计模式学习(13) 23-11 享元模式

文章目录

  • 0.个人感悟
  • [1. 概念](#1. 概念)
  • [2. 适配场景](#2. 适配场景)
    • [2.1 适合的场景](#2.1 适合的场景)
    • [2.2 常见场景举例](#2.2 常见场景举例)
  • [3. 实现方法](#3. 实现方法)
    • [3.1 实现思路](#3.1 实现思路)
    • [3.2 UML类图](#3.2 UML类图)
    • [3.3 代码示例](#3.3 代码示例)
  • [4. 优缺点](#4. 优缺点)
    • [4.1 优点](#4.1 优点)
    • [4.2 缺点](#4.2 缺点)
  • [5. 源码分析](#5. 源码分析)

0.个人感悟

  • 说到享元,第一时间想到的是线程池、String常量池、包装类型缓存池等池化技术。这些都是运用共享技术来复用对象,减少对象开支
  • 使用享元模式难点在于享元对象的确定和划分内部状态、外部状态,我的理解
    • 享元对象: 享元享元,共享元数据,如果不同实例之间有差异,比如个别属性不同,但是有共性,那么可以将不同实例抽象成享元对象,而这些差异属性则划分为内部状态
    • 内部状态: 享元对象自身的状态。可以区分享元对象实例。比如五子棋、围棋、跳棋,棋子进行享元,假如有7种颜色,那么对于棋子的实例而言,一种颜色是一个实例,第一个黑子和第二个黑子取的是同一个实例
    • 外部状态: 享元对象依赖的对象,无法共享,不属于实例属性,比如棋子的位置信息
    • 实际中,内部状态存储于享元对象内部,表现为享元对象属性,外部对象由客户端考虑和传入,通常是享元对象的方法参数

1. 概念

英文定义 (《设计模式:可复用面向对象软件的基础》)

Use sharing to support large numbers of fine-grained objects efficiently.

中文翻译

运用共享技术来有效地支持大量细粒度的对象。

理解

  • 享元模式的核心是共享,通过共享来减少对象数量,节省内存空间
  • 将对象的内部状态 (不变部分)和外部状态(可变部分)分离
  • 内部状态:在多个对象间共享,存储在享元对象内部,不会随环境改变
  • 外部状态:依赖于具体上下文,由客户端保存,在需要时传入享元对象
  • 适用于系统中存在大量相似对象的情况,通过共享减少内存消耗

2. 适配场景

2.1 适合的场景

  1. 系统中有大量相似对象,导致内存开销过大
  2. 对象的大部分状态可以外部化,只有少量状态需要在对象内部维护
  3. 应用程序不依赖于对象标识(即不关心对象是否为同一个实例)
  4. 对象创建成本高,但使用频繁,希望通过共享减少创建开销
  5. 需要缓存某些计算结果或数据,避免重复计算

2.2 常见场景举例

  • 文字编辑器:每个字符(字母、数字)的字体、颜色、大小等可以共享
  • 图形系统:游戏中的粒子效果(火花、子弹),可以共享纹理、形状
  • 数据库连接池:连接对象可以被多个客户端共享使用
  • 网页浏览器:图片、CSS样式等资源可以缓存和共享
  • 棋牌游戏:棋盘上的棋子对象可以共享形状、颜色等属性

3. 实现方法

3.1 实现思路

  1. 分离内部状态和外部状态:分析对象的哪些属性是不变的(内部状态),哪些是变化的(外部状态)
  2. 创建享元接口:定义可以接受外部状态并执行操作的方法
  3. 实现具体享元类:包含内部状态,并实现享元接口
  4. 创建享元工厂:管理享元对象池,确保对象被正确共享
  5. 客户端使用享元:客户端维护外部状态,需要时从工厂获取享元对象并传递外部状态

3.2 UML类图

角色说明

  • Flyweight(抽象享元):定义对象接口,可以接受外部状态
  • ConcreteFlyweight(具体享元):实现抽象享元接口,存储内部状态
  • UnsharedConcreteFlyweight(非共享享元):不需要共享的具体享元类
  • FlyweightFactory(享元工厂):创建和管理享元对象,确保正确共享
  • Client(客户端):维护外部状态,通过享元工厂获取享元对象

3.3 代码示例

背景:网站获取示例,支持新闻、博客形式,如果每次创建一个示例,那么会有很大开销,同时不易于代码复用,可以使用享元模式来解决

外部状态User

java 复制代码
public class User {  
    private String name;  
  
    public User(String name) {  
        this.name = name;  
    }  
  
    public String getName() {  
        return name;  
    }  
  
    public void setName(String name) {  
        this.name = name;  
    }  
}

抽象享元

java 复制代码
public abstract class WebSite {  
  
    /**  
     * @param user 用户  
     * @description 浏览  
     * @author bigHao  
     * @date 2026/1/13  
     **/    
     public abstract void see(User user);  
}

具体享元

java 复制代码
public class ConcreteWebSite extends WebSite {  
    // 内部状态  
    private String type;  
  
    public ConcreteWebSite(String type) {  
        this.type = type;  
    }  
  
    public String getType() {  
        return type;  
    }  
  
    public void setType(String type) {  
        this.type = type;  
    }  
  
    @Override  
    public void see(User user) {  
        System.out.println("ConcreteWebSite,webSite type:" + type + ", user:" + user.getName());  
    }  
}

工厂

java 复制代码
public class WebSiteFactory {  
    public static final String BLOG = "blog";  
  
    public static final String NEWS = "news";  
  
    // 池  
    private Map<String, WebSite> webSiteMap = new HashMap<String, WebSite>(2);  
  
    /**  
     * @param type 网站类型  
     * @return designpattern.flyweight.WebSite  
     * @description 获取网站实例  
     * @author bigHao  
     * @date 2026/1/13  
     **/  
    public WebSite getWebSite(String type) {  
        // 存在则直接取,不存在则放入,保持只有一个类型实例  
        webSiteMap.putIfAbsent(type, new ConcreteWebSite(type));  
        return webSiteMap.get(type);  
    }  
  
    /**  
     * @return int 数量  
     * @description 获取网站实例数量  
     * @author bigHao  
     * @date 2026/1/13  
     **/    public int getWebSiteCount() {  
        return webSiteMap.size();  
    }  
}

客户端

java 复制代码
public class Client {  
    static void main() {  
        // 工厂  
        WebSiteFactory webSiteFactory = new WebSiteFactory();  
  
        // 实例1  
        WebSite webSite1 = webSiteFactory.getWebSite(WebSiteFactory.BLOG);  
        webSite1.see(new User("user1"));  
        System.out.println("网站实例数量: " + webSiteFactory.getWebSiteCount());  
  
        // 实例2  
        WebSite webSite2 = webSiteFactory.getWebSite(WebSiteFactory.NEWS);  
        webSite1.see(new User("user2"));  
        System.out.println("网站实例数量: " + webSiteFactory.getWebSiteCount());  
  
  
        // 实例3  
        WebSite webSite3 = webSiteFactory.getWebSite(WebSiteFactory.BLOG);  
        webSite1.see(new User("user3"));  
        System.out.println("网站实例数量: " + webSiteFactory.getWebSiteCount());  
  
  
        // 实例4  
        WebSite webSite4 = webSiteFactory.getWebSite(WebSiteFactory.NEWS);  
        webSite1.see(new User("user4"));  
        System.out.println("网站实例数量: " + webSiteFactory.getWebSiteCount());  
    }  
}

输出

复制代码
ConcreteWebSite,webSite type:blog, user:user1
网站实例数量: 1
ConcreteWebSite,webSite type:news, user:user2
网站实例数量: 2
ConcreteWebSite,webSite type:blog, user:user3
网站实例数量: 2
ConcreteWebSite,webSite type:news, user:user4
网站实例数量: 2

4. 优缺点

4.1 优点

节约内存,提高性能

  • 大量减少相似对象的创建,显著降低内存占用
  • 减少对象的创建和销毁开销,提高系统性能
    提高复用性
  • 享元对象可以被多个客户端共享使用
  • 内部状态被复用,避免重复存储相同数据
    增强扩展性
  • 可以轻松添加新的享元类型
  • 外部状态的改变不会影响享元对象本身
    符合单一职责原则
  • 将内部状态和外部状态分离,每个类职责更清晰
  • 享元对象专注于内部状态,客户端专注于外部状态
    提高可维护性
  • 通过享元工厂集中管理对象创建,便于统一维护
  • 享元池的状态可以监控和调整

4.2 缺点

增加系统复杂度

  • 需要分离内部状态和外部状态,设计更复杂
  • 增加了享元工厂的管理逻辑
    线程安全问题
  • 共享的享元对象可能被多个线程同时访问
  • 需要确保享元对象的线程安全性
    外部状态管理复杂
  • 客户端需要维护和管理外部状态
  • 外部状态的传递和同步可能带来复杂性
    可能违反封装原则
  • 外部状态需要暴露给享元对象,可能破坏封装性
  • 享元对象需要了解外部状态的接口
    不适用于所有场景
  • 如果对象的状态大部分都是可变的,享元模式的收益有限
  • 如果对象间的差异很大,共享的意义不大

5. 源码分析

JDK中Integer类的缓存机制是享元模式的典型应用

跟踪代码可以看到其中使用了缓存数组

java 复制代码
// Integer类源码分析
public final class Integer extends Number implements Comparable<Integer> {
    
    // 内部类:整数缓存
    private static class IntegerCache {
        static final int low = -128;  // 缓存下限
        static final int high;        // 缓存上限
        static final Integer[] cache; // 缓存数组
        
        static {
            // 默认上限是127
            int h = 127;
            // 可以通过JVM参数调整上限
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // 最大数组大小限制
                    h = Math.min(i, Integer.MAX_VALUE - (-low) - 1);
                } catch (NumberFormatException nfe) {
                    // 如果配置无效,忽略
                }
            }
            high = h;
            
            // 初始化缓存数组
            cache = new Integer[(high - low) + 1];
            int j = low;
            for (int k = 0; k < cache.length; k++) {
                cache[k] = new Integer(j++);
            }
        }
        
        private IntegerCache() {} // 私有构造函数
    }
    
    // valueOf方法:使用缓存(享元工厂方法)
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high) {
            // 如果在缓存范围内,返回缓存对象
            return IntegerCache.cache[i + (-IntegerCache.low)];
        }
        // 否则创建新对象
        return new Integer(i);
    }
    
    // 其他方法...
}

角色分析 :
内部状态和外部状态的分离

  • 内部状态:包装对象的值(如Integer的int值)是不可变的
  • 外部状态 :无(因为这些对象不需要外部状态)
    享元工厂
  • 各个包装类的内部缓存类(IntegerCache、LongCache等)
  • 通过静态初始化块创建缓存对象
    客户端使用
  • 通过valueOf()方法获取享元对象
  • 自动判断是否使用缓存对象

设计优势

  1. 显著减少内存占用:大量使用小整数和字符时,避免重复创建对象
  2. 提高性能:对象创建和垃圾回收的开销大大减少

参考:

相关推荐
week_泽2 小时前
第3课:构建AI代理系统面临的挑战 - 学习笔记_3
人工智能·笔记·学习·ai agent
week_泽2 小时前
第8课:LangGraph Memory管理机制与实现方案 - 学习笔记_8
java·笔记·学习·ai agent
MhZhou04122 小时前
开源 动态课程学习的单细胞聚类
学习
Flamingˢ2 小时前
Verilog中reg与wire的区别:从语法到实战
学习·fpga开发·硬件工程
heartbeat..2 小时前
Spring Boot 学习:原理、注解、配置文件与部署解析
java·spring boot·学习·spring
信奥胡老师2 小时前
P14917 [GESP202512 五级] 数字移动
开发语言·数据结构·c++·学习·算法
深情的小陈同学2 小时前
工作学习笔记 —— 解决刷新缓存问题
笔记·学习·ai编程
好奇龙猫2 小时前
大学院-筆記試験練習:数据库(データベース問題訓練) と 软件工程(ソフトウェア)(8)
学习
天上的光2 小时前
车道线检测
学习