享元模式

找出相似对象之间的共有特征,然后复用这些特征,以通过池的功能来解决内存。享元模式通过共享技术实现相同或者相似对象的重用,在逻辑上看起来都有一个单独的对象,但实际上在物理上他们确是共享的同一个单元。

在Java中,String类(常量池)、Integer类(0-128始终存储在内存中)都是用了享元模式。享元模式通常与工厂模式一起连用。

享元模式中享元对象有两种状态:

  1. 内部状态,不会随着环境的改变而改变的部分,这部分通常作为示例的成员变量,在创建时就跟随实例存在

  2. 外部状态,随着环境改变会发生改变的部分,也就是不可共享的部分,这部分通常作为方法参数

代码示例:

// 抽象享元类
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中的享元模式

在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);
    }
}

最后更新于