手游网站建设的宗旨,dede企业网站源码,横岗网站设计,gta房产网站建设中文章目录 1 基本介绍2 案例2.1 Digit 接口2.2 Color 枚举2.3 BigDigit 类2.4 DigitFactory 类2.5 Client 类2.6 Client 类的测试结果2.7 总结 3 各角色之间的关系3.1 角色3.1.1 Flyweight ( 抽象享元 )3.1.2 ConcreteFlyweight ( 具体享元 )3.1.3 UnsharedFlyweight ( 非享元 )… 文章目录 1 基本介绍2 案例2.1 Digit 接口2.2 Color 枚举2.3 BigDigit 类2.4 DigitFactory 类2.5 Client 类2.6 Client 类的测试结果2.7 总结 3 各角色之间的关系3.1 角色3.1.1 Flyweight ( 抽象享元 )3.1.2 ConcreteFlyweight ( 具体享元 )3.1.3 UnsharedFlyweight ( 非享元 )3.1.4 FlyweightFactory ( 享元工厂 )3.1.5 Client ( 客户端 ) 3.2 类图 4 注意事项5 在源码中的使用6 优缺点7 适用场景8 总结 1 基本介绍
享元模式Flyweight Pattern是一种 结构型 设计模式其核心思想是通过 共享某些对象 来 复用 它们和单例模式的单例有相似之处——共享实例对象。
此处的翻译并非直译中文和英文对应的含义如下
对于中文 享元模式“享元”实际上是“共享实例对象”的意思。对于英文 FlyweightFlyweight 的中文翻译是 蝇量级这个概念原本是拳击比赛的一种重量级用在此处表示这种模式可以使程序变得更“轻”即 占用的内存更少。
2 案例
本案例实现了 打印不同颜色的大数字虽然只有 3 种颜色、3 种大数字。
2.1 Digit 接口
public interface Digit { // 数字接口void print(Color color); // 按照指定颜色打印对象中所存储的数
}2.2 Color 枚举
public enum Color { // 颜色的枚举GREEN(\u001B[32m), // 绿色BLUE(\u001B[34m), // 蓝色PURPLE(\u001B[35m); // 紫色Color(String colorStr) {this.colorStr colorStr;}private String colorStr; // 保存颜色的字符串public String getColorStr() {return colorStr;}
}2.3 BigDigit 类
import java.io.PrintStream;public class BigDigit implements Digit { // 大数字private String pattern; // 大数字的图像public BigDigit(int num) {switch (num) {case 1:pattern DIGIT_1_PATTERN;break;case 2:pattern DIGIT_2_PATTERN;break;case 3:pattern DIGIT_3_PATTERN;break;default:pattern 尚未实现敬请期待;}}// 按照指定的颜色打印大数字的图像Overridepublic void print(Color color) {PrintStream defaultPrintStream System.out; // 保存默认的打印流System.setOut(new ColoredDigitPrintStream(color)); // 将打印流更换为指定颜色的打印流System.out.println(pattern);System.setOut(defaultPrintStream); // 还原默认的打印流}private static final String DIGIT_1_PATTERN ......................##..........######..............##..............##..............##..............##..........##########....................; // 大数字 1 的图像private static final String DIGIT_2_PATTERN ....................######........##......##..............##..........####..........##............##..............##########....................; // 大数字 2 的图像private static final String DIGIT_3_PATTERN ....................######........##......##..............##..........####................##......##......##........######......................; // 大数字 3 的图像private static class ColoredDigitPrintStream extends PrintStream { // 带有颜色的数字打印流private Color color; // 打印流的颜色public ColoredDigitPrintStream(Color color) {super(System.out);this.color color;}Overridepublic void println(String x) {super.println(this.color.getColorStr() x DEFAULT_COLOR);}private static final String DEFAULT_COLOR \u001B[0m; // 默认的字符颜色}
}2.4 DigitFactory 类
public class DigitFactory { // 数字的工厂// 数字池存储 int 数 与 数字 的映射private static MapInteger, Digit digitPool new HashMap();/*** 获取指定 int 数对应的数字* 注意本方法是 synchronized 的防止多个线程同时创建多个共享实例类似于单例模式** param num 指定的 int 数* return 返回指定 int 数对应的数字*/public synchronized static Digit getInstance(int num) {if (!digitPool.containsKey(num)) { // 如果 digitPool 中不存在 num 对应的数字digitPool.put(num, new BigDigit(num)); // 则创建新的大数字}return digitPool.get(num); // 返回 digitPool 中 num 对应的数字}
}2.5 Client 类
public class Client { // 客户端测试大数字的输出public static void main(String[] args) {// 可以通过 digit1 打印不同颜色的 1Digit digit1 DigitFactory.getInstance(1);digit1.print(Color.BLUE);digit1.print(Color.PURPLE);Digit digit2 DigitFactory.getInstance(2);digit2.print(Color.GREEN);Digit digit3 DigitFactory.getInstance(3);digit3.print(Color.PURPLE);Digit digit3_1 DigitFactory.getInstance(3);System.out.println(digit3 digit3_1); // true因为 digit3 和 digit3_1 是同一个对象}
}2.6 Client 类的测试结果
注意文章中无法显示大数字的颜色可以自己在 IDEA 上运行一下即可看到不同的颜色。
................
......##........
..######........
......##........
......##........
......##........
......##........
..##########....
................
................
......##........
..######........
......##........
......##........
......##........
......##........
..##########....
................
................
....######......
..##......##....
..........##....
......####......
....##..........
..##............
..##########....
................
................
....######......
..##......##....
..........##....
......####......
..........##....
..##......##....
....######......
................
true2.7 总结
通过使用享元模式减少了创建大数字对象的机会从而降低了程序的内存占用通过 Map 管理生成的实例避免了多次 new 新对象耗费的时间。可谓是时间和空间上的双赢。
此外认真观察大数字的两个“属性”图像 和 颜色可以发现
图像是固定的不论任何情况都不会改变所以图像是 应当共享的信息将其放到 BigDigit 中。颜色不是固定的随着 Client 需要的颜色而变化所以颜色是 不应当共享的信息将其放到其他类 Color 中。
3 各角色之间的关系
3.1 角色
3.1.1 Flyweight ( 抽象享元 )
该角色负责 将 UnsharedFlyweight 角色作为参数定义 ConcreteFlyweight 角色需要实现的方法。如果系统的功能很简单则不需要该角色。本案例中Digit 接口扮演该角色。
3.1.2 ConcreteFlyweight ( 具体享元 )
该角色负责 实现 Flyweight 角色定义的方法定义可以共享的部分。本案例中BigDigit 类扮演该角色。
3.1.3 UnsharedFlyweight ( 非享元 )
该角色负责 定义无法共享的部分作为参数传入 Flyweight 角色定义的方法中。本案例中Color 枚举扮演该角色。
3.1.4 FlyweightFactory ( 享元工厂 )
该角色负责 生成可共享的 Flyweight 角色内部有一个 池 的字段来保存已生成的 Flyweight 实例当传入相同的参数时返回这些已有的实例。要注意 getInstance() 方法需要被 synchronized 关键字修饰以保证线程安全。本案例中DigitFactory 类扮演该角色。
3.1.5 Client ( 客户端 )
该角色负责 使用 FlyweightFactory 角色来生成 Flyweight 角色。本案例中Client 类扮演该角色。
3.2 类图 说明FlyweightFactory 角色的 pool 字段 和 getInstance() 方法都是 静态 的使用了 简单静态工厂模式。
4 注意事项
划分外部状态和内部状态正确划分 内部状态 和 外部状态 是享元模式应用的关键。 内部状态指对象中 可以共享的信息这部分信息 存储在享元对象内部不会随环境的改变而改变。外部状态指对象中 不可共享的信息这部分信息 在享元对象外部用新的对象来存储随环境改变而改变在方法调用时 以参数形式传入。 生成共享实例的工厂享元模式中的类通常需要一个 工厂 来 控制对象的 创建 和 管理。当客户对象请求一个享元对象时享元工厂会 先检查系统中是否存在符合要求的享元对象如果存在则提供给客户如果不存在则创建一个新的享元对象。线程安全问题由于享元模式中的对象可能被多个线程下的客户端 共享因此必须确保这些对象在并发环境下是线程安全的。这可能需要使用 同步机制 来 保护共享对象的内部状态。性能考虑虽然享元模式可以显著减少对象的创建和内存使用但 读取外部状态 可能 会使运行时间稍微变长。在性能敏感的应用中需要仔细评估这一点。
5 在源码中的使用
在 JDK 中的 Integer 类就使用了享元模式的思想如果不设置 VM 参数则为常用的 int 数范围为 [-128, 127]提前初始化其对应的 Integer 对象。
在调用 Integer.valueOf() 时如果传入的参数在 [-128, 127] 之内则返回提前初始化的 Integer 对象否则就为传入的参数初始化一个新的 Integer 对象。
Integer 类的部分代码如下所示
// java.lang.Integer 类
public final class Integer extends ... implements ... {public static Integer valueOf(int i) { // 相当于 享元工厂 的 getInstance()// 如果 i 在范围 [low, high] 内则直接返回提前初始化好的 Integer 对象if (i IntegerCache.low i IntegerCache.high)return IntegerCache.cache[i (-IntegerCache.low)];return new Integer(i);}private static class IntegerCache { // Integer 的缓存static final int low -128; // 最小的 int 数static final int high; // 最大的 int 数可能由 VM 参数指定static final Integer[] cache; // 保持不变的常量池static Integer[] archivedCache; // archive 中的缓存static {// high 的值可能被 VM 参数所指定int h 127;String integerCacheHighPropValue VM.getSavedProperty(java.lang.Integer.IntegerCache.high);if (integerCacheHighPropValue ! null) {try {h Math.max(parseInt(integerCacheHighPropValue), 127);// 缓存数组的最大长度是 Integer.MAX_VALUEh Math.min(h, Integer.MAX_VALUE - (-low) -1);} catch( NumberFormatException nfe) {// 如果这个属性不能被解析成一个 int 数则忽略这个异常}}high h;// 如果可能的话从 archive 中加载 IntegerCache.archivedCacheCDS.initializeFromArchive(IntegerCache.class); // 此方法是 native 方法int size (high - low) 1;// 如果 archive 缓存 存在 且 足够大则使用它否则构建新的缓存if (archivedCache null || size archivedCache.length) {// 将范围 [low, high] 内的 Integer 对象提前放入常量池Integer[] c new Integer[size];int j low;for(int i 0; i c.length; i) {c[i] new Integer(j);}archivedCache c;}cache archivedCache;// 确保范围 [-128, 127] 内的 Integer 对象提前将其放入常量池assert IntegerCache.high 127;}private IntegerCache() {}}
}说明
Integer 类中 写一个静态内部类来创建共享的对象实例这样可以避免给 valueOf() 方法添加 synchronized 修饰因为 类的加载是线程安全 的所以不可能生成两个相同值的 Integer 共享实例。这种思想可以类比到 单例模式 的 静态内部类实现 中。在 Integer 类中 valueOf() 方法相当于 FlyweightFactory 角色的 getInstance() 方法。IntegerCache.cache 相当于 FlyweightFactory 角色的 pool 字段用于存储已创建的共享实例。 Integer 类并没有完全使用享元模式它只是提前初始化了部分高频使用的 Integer 对象将其共享从而减少 valueOf() 方法创建过多的对象引发 内存溢出 问题。此外这么做还可以减少构建共享对象所花费的时间使 Integer 类更加高效。
6 优缺点
优点
减少内存消耗享元模式通过 共享对象的内部状态 来 减少内存占用从而避免为每个相似的对象都创建新的实例。提高性能由于 减少了对象的创建和销毁次数享元模式可以 提高系统的性能。特别是在 频繁创建和销毁对象 的场景中享元模式能够显著减少这些操作带来的开销。降低系统复杂性享元模式将对象的状态分为 内部状态 和 外部状态使得系统更容易理解和维护。内部状态 由 享元对象 管理外部状态 由 客户端 管理这种分离 降低了系统的复杂性并有效地 降低了对象间的耦合度。
缺点
增加编程复杂性实现享元模式需要将对象的状态分为 内部状态 和 外部状态这要求开发者对系统的 状态管理 有深入的理解。同时区分内部状态和外部状态可能会使代码逻辑变得复杂增加编程的难度。可能引入线程安全问题如果多个线程同时访问和修改共享对象可能会导致线程安全问题。因此在使用享元模式时需要特别注意线程安全的处理。可能增加运行时开销由于享元对象的 外部状态 是通过参数传递给享元对象的这可能会增加运行时的开销。特别是在外部状态较多或频繁变化的情况下这种开销可能会更加明显。
7 适用场景
相似对象的大量使用当系统中存在大量相似对象即这些对象只有少部分数据不同时使用享元模式可以显著减少对象的数量从而降低内存消耗。对象具有许多共同属性当对象具有许多 共同属性而这些属性可以 被所有实例共享 时使用享元模式可以避免在每个实例中重复存储这些属性从而节省内存。频繁创建和销毁对象如果系统中频繁地创建和销毁大量对象这会导致大量的内存分配和回收操作从而降低系统的性能。使用享元模式可以减少对象的创建和销毁次数从而提高性能。需要快速响应时间的系统在一些需要 快速响应时间 的系统中如实时游戏、实时交易系统等减少对象的创建和销毁时间 是非常重要的。享元模式可以通过减少对象的数量来降低这些操作的开销从而提高系统的响应时间。
8 总结
享元模式 是一种 结构型 设计模式其核心思想是通过 共享某些对象 来 复用 它们和单例模式的单例有相似之处可以减少 内存 和 时间 的消耗。
然而在使用这种模式时不能随心所欲共有三点需要特别考虑
先明确对象的 外部状态 和 内部状态然后将 内部状态 放到享元类中使用享元工厂生产共享实例将 外部状态 放到另外的类中在调用共享实例的方法时将其传入。由于享元模式可能应用到多线程的环境中所以在工厂生成共享实例时需要 保证线程安全。对于一个共享实例的多个引用如果使用其中一个引用修改这个共享实例那么会导致所有引用指向的实例都被修改这也是共享的坏处之一应该尽可能保证共享实例不会被修改。