享元模式
最后更新于
这有帮助吗?
找出相似对象之间的共有特征,然后复用这些特征,以通过池的功能来解决内存。享元模式通过共享技术实现相同或者相似对象的重用,在逻辑上看起来都有一个单独的对象,但实际上在物理上他们确是共享的同一个单元。
在Java中,String类(常量池)、Integer类(0-128始终存储在内存中)都是用了享元模式。享元模式通常与工厂模式一起连用。
享元模式中享元对象有两种状态:
内部状态,不会随着环境的改变而改变的部分,这部分通常作为示例的成员变量,在创建时就跟随实例存在
外部状态,随着环境改变会发生改变的部分,也就是不可共享的部分,这部分通常作为方法参数
代码示例:
// 抽象享元类
public abstract class Flyweight {
// 享元类的操作,exState代表外部状态,随时可变
public abstract void operation(String exState);
}
// 可共享的享元具体类
public class ConcreteFlyweight extends Flyweight{
// 内部状态,创建时就是确定的,不可变的
private String inState;
public ConcreteFlyweigth(String inState) {
this.inState = inState;
}
@Override
public void operation(String exState) {
// ...
}
}
// 不可共享的实例
public class UnsharedFlyweight extends Flyweight{
// 不可共享的没有内部状态,因为他不需要内部状态来保证在池中的唯一性
@Override
public void operation(String exState) {
// ...
}
}
// 享元工厂类,作为享元对象的享元池,并且提供获取与增加的方法
// 从享元池中获取享元对象时,会先从享元池获取,有则返回,没有创建新的并返回
public class FlyweightFactory {
private Map<String, Flyweight> pool = new HashMap<>();
// 创建池时候,积极加载一些默认的值
public FlyweightFactory() {
pool.put("A", new ConcreteFlyweigth("A"));
pool.put("B", new ConcreteFlyweigth("B"));
pool.put("C", new ConcreteFlyweigth("C"));
}
// 通过唯一的内部状态获取吃中的享元对象
public Flyweight getFlyweight(String key) {
if (!pool.containsKey(key)) {
ConcreteFlyweight fw = new ConcreteFlyweight(key);
pool.put(key, fw);
}
return pool.get(key);
}
}
围棋中的黑白棋子,大量的黑子和大量的白子他们的属性都是相同的,只有颜色不同,所以享元模式非常适合这种情况。其中棋子就是享元类,而颜色就是他的内部状态。
/**
* 抽象享元类: 五子棋类
**/
public abstract class GobangFlyweight {
public abstract String getColor();
public void display(){
System.out.println("棋子颜色: " + this.getColor());
}
}
/**
* 共享享元类-白色棋子
**/
public class WhiteGobang extends GobangFlyweight{
@Override
public String getColor() {
return "白色";
}
}
/**
* 共享享元类-黑色棋子
**/
public class BlackGobang extends GobangFlyweight {
@Override
public String getColor() {
return "黑色";
}
}
/**
* 享元工厂类-生产围棋棋子,使用单例模式进行设计
**/
public class GobangFactory {
private static GobangFactory factory = new GobangFactory();
private static Map<String,GobangFlyweight> pool;
//设置共享对象的内部状态,在享元对象中传递
private GobangFactory() {
pool = new HashMap<String,GobangFlyweight>();
GobangFlyweight black = new BlackGobang(); //黑子
GobangFlyweight white = new WhiteGobang(); //白子
pool.put("b",black);
pool.put("w",white);
}
//返回享元工厂类唯一实例
public static final GobangFactory getInstance(){
return SingletonHolder.INSTANCE;
}
//静态内部类-单例
private static class SingletonHolder{
private static final GobangFactory INSTANCE = new GobangFactory();
}
//通过key获取集合中的享元对象
public GobangFlyweight getGobang(String key){
return pool.get(key);
}
}
public class Client {
public static void main(String[] args) {
//获取享元工厂对象
GobangFactory instance = GobangFactory.getInstance();
//获取3颗黑子
GobangFlyweight b1 = instance.getGobang("b");
GobangFlyweight b2 = instance.getGobang("b");
GobangFlyweight b3 = instance.getGobang("b");
System.out.println("判断两颗黑子是否相同: " + (b1 == b2));
//获取2颗白子
GobangFlyweight w1 = instance.getGobang("w");
GobangFlyweight w2 = instance.getGobang("w");
System.out.println("判断两颗白子是否相同: " + (w1 == w2));
//显示棋子
b1.display();
b2.display();
b3.display();
w1.display();
w2.display();
}
}
在Integer中有享元池,他会缓存0-128之间的Integer对象实例,所以一下代码的结果是一个true一个false:
Integer i1 = 56;
Integer i2 = 56;
Integer i3 = 129;
Integer i4 = 129;
System.out.println(i1 == i2); // true
System.out.println(i3 == i4); // false
在上述代码的执行过程中,由于自动装箱,等同于如下代码:
Integer i1 = Integer.valueOf(56);
Integer i2 = Integer.valueOf(56);
Integer i3 = Integer.valueOf(129);
Integer i4 = Integer.valueOf(129);
System.out.println(i1 == i2); // true
System.out.println(i3 == i4); // false
查看valueOf方法的源码,会看到大于-128且小于127的的Integer对象,不会创建,而是会直接从享元池IntegerCache中获取,所以在这个范围的Integer对象是始终指向同一个内存地址的:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
/**
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
*
* The cache is initialized on first usage. The size of the cache
* may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
* During VM initialization, java.lang.Integer.IntegerCache.high property
* may be set and saved in the private system properties in the
* sun.misc.VM class.
*/
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
如果想要改写缓存区域的大小,可以更改jvm变量:
//方法一:
-Djava.lang.Integer.IntegerCache.high=255
//方法二:
-XX:AutoBoxCacheMax=255
public class ASenderStrategy extends AbstractMqSenderStrategy {
@Override
protected MessageType getMessageType() {
return MessageType.TYPE_A;
}
@Override
public void doSend(String message) {
}
}
public class MqSenderStrategyFactory {
private static final ConcurrentHashMap<MessageType, MqSenderStrategy> SENDER_MAP;
static {
SENDER_MAP = new ConcurrentHashMap<>(MessageType.values().length);
ASenderStrategy as = new ASenderStrategy();
ASenderStrategy bs = new BSenderStrategy();
SENDER_MAP.put(as.getMessageType(), as);
SENDER_MAP.put(bs.getMessageType(), bs);
}
public static MqSenderStrategy getSender(MessageType messageType) {
return SENDER_MAP.get(messageType);
}
}