文章目录
I . 享元模式 简介
II . 享元模式 内部状态 和 外部状态
III . 享元模式 适用场景
IV . 享元模式 优缺点
V . 享元模式 相关模式
VI . 享元模式 相关角色
I . 享元模式 简介
1 . 享元模式 简介 : 享元模式的核心是 对象池 , 使用对象时 , 先从对象池中获取对象 , 如果对象池中没有 , 创建一个 , 放入对象池 , 然后再从对象池中获取 ; ( 只能从对象池中拿对象 , 不能自己创建 )
① 设计模式类型 : 结构性 ;
② 享元模式 概念 : 通过减少创建对象数量 , 改善应用中使用对象的结构 , 使用 共享对象 ( 对象池中的对象 ) , 支持多个 细粒度对象 ( 使用时的大量对象 ) ;
③ 好处 : 减少创建对象的数量 , 从而减少内存的占用 , 提高性能 ;
2 . 细粒度对象 和 共享对象 : 目的是为了提高程序性能 ;
① 细粒度对象 : 是内存中的数量庞大的对象 ; 实际使用的数量庞大的对象 ;
② 共享对象 : 多个细粒度对象共享的部分数据 ; 对象缓存池中存储的对象 ;
③ 举例说明 : 使用字符串值 “abc” , 首次使用 , 创建该字符串 , 将其放入字符串缓存池中 , 这个缓存池中的字符串就是 "共享对象" , 应用中要大量使用 “abc” 字符串 , 比如使用 10 万个 “abc” 字符串对象 , 这 10 万个字符串对象就是 "细粒度对象" , 此时肯定不会创建这么多对象 , 这 10 万个对象使用时从字符串缓存池中查找缓存的那个 "共享对象" 即可 , 这样节省了很大的内存开销 ;
3 . 享元模式示例 : Java 的 String 类型就是用了享元模式的设计模式 ;
① 定义字符串 : String str = "Hello" ;
② 内存中已有该字符串 : 如果之前已经有该字符串 , 就直接将字符串缓存池中的字符串返回 ,
③ 新字符串 : 如果内存中没有该字符串 , 就创建一个新的字符串 , 放入缓存池中 ;
享元模式就是池技术 , 如字符串池 , 数据库连接池 等 ;
使用对象时 , 先从池中查找 , 没有找到再创建该对象 , 然后放入对象池中 ;
4 . 享元模式使用策略 : 用户想要调用一个对象 , 去对象池中查找 , 如果对象池中有该对象 , 那么直接使用该对象 , 如果没有 , 创建该对象 , 放入对象池中 , 然后再从对象池中获取该对象 ;
对象对比 : 这里涉及到一个问题 , 如何确定对象池中的对象是不是用户想要使用的对象呢 ?
5 . 引入 内部状态 和 外部状态 : 对象对比问题引出这两个概念 , 对象中有很多数据 , 那么使用什么数据来确定两个对象是否一致呢 , 这里使用 对象的 外部状态 来确定 ;
① 内部状态 : 对象的内部状态不能作为对象对比的依据 , 所有对象的内部状态都是一样的数据 ;
② 外部状态 : 对象的外部状态每个都不一样 , 每个对象都有一个唯一 外部状态 值 , 类似于 身份证 , 哈希码 这一类的信息 ;
③ 身份标识 : 在线程池中 , 使用外部状态信息 , 唯一确定一个对象 , 作为对象的标识信息 ;
II . 享元模式 内部状态 和 外部状态
1 . 概念引入 : 区分这两个概念的目的是为了维护享元模式的对象池 , 当用户想要使用某个对象时 , 如何确定对象池中的对象是否是用户想要调用的对象呢 , 这里就需要一些数据进行对比 , 数据一致 , 就说明是用户想要的对象 , 数据不一致 , 就需要创建新对象 , 放入对象池 ;
① 内部状态 : 有些数据所有的对象都一样 , 显然不能当做对象一致性对比的依据 , 这就是 内部状态 ;
② 外部状态 : 有些数据每个对象都不一样 , 根据该数据确定对象的唯一性 , 相当于 哈希码 , 身份证号 , 档案编号 这一类的数据 , 这就是外部状态 ;
内部状态 和 外部状态 本质是 信息数据
2 . 内部状态 ( 共享信息 ) : 在享元模式中的对象中 , 不随环境改变而改变的信息 ;
① 共享信息 : 内部状态就是可以被共享的信息 ;
② 存储位置 : 该信息存储在享元对象内部 ;
③ 存储形式 : 该信息作为对象的附加数据 , 不在具体的对象中存储 , 可以被多个对象共享 ;
3 . 外部状态 ( 不可共享信息 ) : 随着外部环境改变 , 对象内部跟着改变 , 这部分内容就不能进行共享 ;
不可共享 : 外部状态不可被共享 , 每个值都必须在不同的对象中维护 ;
III . 享元模式 适用场景
1 . 享元模式 适用场景 :
① 底层开发 : 某个系统的底层开发 , 对性能要求比较高 , 可使用享元模式 ;
② 缓冲池 : 系统中实例对象数量庞大 , 需要缓冲池处理这些对象 ;
2 . 享元模式使用前提 : 系统中存在大量的对象 , 这些对象状态大部分功能可以外部化 , 将这些功能抽离出来 , 只在内存中保留一份 ;
① 分离对象功能 : 系统中如果内存中持有大量对象 , 可能会溢出 , 将这些对象相同的部分分离出来 ;
② 用户调用行为 : 如果有相同的业务请求 , 则优先使用内存中已有的对象进行处理 , 避免使用大量相同的对象 ;
③ 注意 : 这里只有在内存中有大量相同对象时 , 才考虑享元模式 , 如果内存中某类型的对象数量较少 , 没有必要使用该模式 ;
IV . 享元模式 优缺点
1 . 享元模式 优点 :
① 降低内存占用 : 减少在内存中创建对象的数量 , 节省了内存 , 提高了效率 ;
② 减少创建对象开销 : 创建对象时需要占用一定的开销 , 如 new 操作 ; 可能构造函数中有访问 文件 , 数据库 , 网络 等操作 , 也可以避免这些开销 ;
2 . 享元模式 缺点 :
① 线程安全问题 : 在类中为了追求性能 , 一般使用的是 HashMap , ArrayList 等数据 , 这些数据结构都是线程不安全的 ; 使用 HashTable , Vector 线程安全了 , 但是性能会下降很多 ; 折中使用 ConcurrentHashMap 等 concurrent 包下的集合 ;
② 增加复杂性 : 将一个类拆解成多个类 , 系统复杂性肯定增加了 ;
V . 享元模式 相关模式
1 . 享元模式 与 代理模式 : 代理模式需要代理某个类 , 生成该类需要花费较多的资源和时间 , 可以使用享元模式提高处理速度 ;
2 . 享元模式 与 单例模式 : 容器单例模式 , 复用对象 ;
VI . 享元模式 相关角色
1 . 抽象享元角色 : 抽象类 , 其中定义了 内部对象 , 外部对象 , 抽象行为 ;
① 内部对象 : 享元模式中 , 不关心该类数据 ;
② 外部对象 : 该值只能设置一次值 , 不能二次赋值 , 否则会造成对象池管理混乱 ; 一般要设置成 final 类型的 , 在构造函数中赋值 ;
③ 抽象行为 : 这是客户调用的方法 ;
客户使用享元模式时 , 创建的对象就是 抽象的享元角色 对象 , 调用的是抽象行为 ;
享元工厂管理时 , 也管理 抽象享元角色 对象 ;
2 . 具体享元角色 : 在构造函数中设置外部状态 , 实现自己的业务逻辑 ;
3 . 享元工厂角色 : 在享元工厂中 , 维护对象池 , 当用户调用 享元对象 时 , 从对象池中获取该对象 , 如果没有获取到 , 那么创建新的 享元对象 , 放入对象池中 , 并返回该对象 ;
4 . 用户调用 : 用户声明 抽象享元类对象 , 调用其定义的抽象行为 ;