设计模式之享元模式
介绍
享元模式(Flyweight Pattern)是一种结构型设计模式,旨在通过共享对象来减少内存使用和提高性能。它主要适用于当大量相似的对象需要被创建时,通过共享这些相似对象的共同部分,减少重复创建对象的开销。
在享元模式中,将对象分为两种类型:
-
内部状态(Intrinsic State):是对象的固定、不可变的部分,在创建对象时确定并共享。这些信息在对象的整个生命周期中都不会改变。
-
外部状态(Extrinsic State):是对象的变化、可变的部分,在使用对象时传递给对象,并且可能在运行时改变。外部状态并不影响对象的共享,因此可以在不同的上下文中共享对象。
享元模式的主要目标是最大程度地减少内存使用,通过共享内部状态,将相似对象的数据存储在共享池中,而外部状态可以在运行时传递,使得相同或相似的对象可以被多次重复使用,从而节省了大量的内存和资源。
下面是享元模式的一般实现方式:
-
创建享元接口(Flyweight):定义了享元对象需要实现的方法。
-
创建具体享元类(ConcreteFlyweight):实现了享元接口,并包含了内部状态。具体享元对象必须是可共享的,即内部状态在创建后不应该被修改。
-
创建享元工厂类(FlyweightFactory):负责管理和创建享元对象。它维护一个享元池(Flyweight Pool),用于存储已经创建的享元对象。在需要时,工厂类会根据客户端请求返回已有的享元对象,或者创建新的享元对象。
-
客户端代码:通过享元工厂来获取享元对象,并传递外部状态给享元对象。
使用享元模式的关键是要识别出内部状态和外部状态,并将内部状态进行共享,而外部状态通过传递来实现个性化。这样可以极大地减少对象的数量,提高系统性能,并节省内存空间。
享元模式在许多地方有应用,比如线程池、字符串池等。它适用于需要大量相似对象的场景,帮助提高程序的效率和性能。
Coding
创建享元接口
public interface Shape {
void draw();
}
创建具体享元类
public class Circle implements Shape {
private String color; //外部状态,由外部去传递
//长度
private Integer radius; //内部状态,不可变,
public Circle(String color) {
this.color = color;
this.radius = 5;
}
@Override
public void draw() {
System.out.println("绘制一个半径为 " + radius + "、颜色为 " + color + " 的圆");
}
}
创建享元工厂类
public class ShapeFactory {
private static final Map<String, Shape> circleMap = new HashMap<>();
public static Shape getCircle(String color) {
Shape circle = circleMap.get(color);
if (circle == null) {
circle = new Circle(color);
circleMap.put(color, circle );
System.out.println("创建一个颜色为 " + color + " 的圆对象");
}
return circle ;
}
}
客户端代码
public class Client {
public static void main(String[] args) {
// 获取红色圆对象
Shape redCircle = ShapeFactory.getCircle("红色");
redCircle.draw();
// 获取蓝色圆对象
Shape blueCircle = ShapeFactory.getCircle("蓝色");
blueCircle.draw();
// 再次获取红色圆对象,应该直接从缓存中获取,不会创建新对象
Shape anotherRedCircle = ShapeFactory.getCircle("红色");
anotherRedCircle.draw();
}
}
结果
创建一个颜色为 红色 的圆对象
绘制一个半径为 5、颜色为 红色 的圆
创建一个颜色为 蓝色 的圆对象
绘制一个半径为 5、颜色为 蓝色 的圆
绘制一个半径为 5、颜色为 红色 的圆
可以看到第3次没有创建红色的圆,注意到在第一个步骤中已经创建了一个红色圆对象,并将其放入了 circleMap 中。所以第二次获取红色圆对象时,直接从缓存中获取,而不是再次创建新的对象。
源码场景
Integer类在Java中运用了享元模式的概念,它通过缓存常用的整数对象来节省内存和提高性能。
在Java中,整数范围内的数值(通常在-128到127之间)被缓存为Integer对象,以便重复使用相同的对象。这是通过Integer类的内部静态类IntegerCache实现的。
下面是Integer类的相关源码片段:
public final class Integer extends Number implements Comparable<Integer> {
// ...
private static classIntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// 配置缓存的上界,可以通过JVM参数来调整
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++);
}
}
//这种设计使得在整数范围内频繁使用的整数值可以重复使用同一个`Integer`对象,从而节省了内存开销。同时,通过缓存机制,可以提高整数对象的获取速度,提高了性能。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
// ...
}
在Integer类中,有一个私有的静态内部类IntegerCache,它定义了一个缓存数组cache,用于存储整数对象。
在静态代码块中,IntegerCache会根据配置的上界和默认范围(-128到127)来初始化缓存数组。对于在缓存范围内的整数值,valueOf()方法会直接返回缓存中的对象,而不是每次都创建新的对象。对于超出缓存范围的整数值,valueOf()方法会创建一个新的Integer对象。
总结起来,Integer类通过缓存常用的整数对象实现了享元模式的概念,从而在整数范
总结
在享元模式中,通过共享相似对象来减少对象的创建。当需要创建对象时,首先检查缓存中是否存在相应的对象。如果存在,直接从缓存中获取;如果不存在,才会创建新的对象并放入缓存中。这样可以节省内存空间,避免创建大量相同属性的对象,提高系统的性能和效率。