JDK设计模式(十)享元模式

1、定义

采用一个共享来避免大量拥有相同内容对象的开销。

2、解决的问题

使用共享物件,用来尽可能减少内存使用量,以及分享资讯给尽可能多的相似物件;适合用于只是因重复而导致使用无法令人接受的大量内存的大量物件。

3、模式中的角色

内蕴状态存储在享元内部,不会随环境的改变而有所不同,是可以共享的;外蕴状态是不可以共享的,它随环境的改变而改变的,因此外蕴状态是由客户端来保持(因为环境的变化是由客户端引起的)。在每个具体的环境下,客户端将外蕴状态传递给享元,从而创建不同的对象出来。享元模式可分为:单纯享元模式和复合享元模式。

1、 抽象享元角色(Flyweight):为具体享元角色规定了必须实现的方法,而外蕴状态就是以参数的形式通过此方法传入。在Java中可以由抽象类、接口来担当。

2、具体享元角色(ConcreteFlyweight):实现抽象角色规定的方法。如果存在内蕴状态,就负责为内蕴状态提供存储空间。

3、复合享元角色(ConcreteCompositeFlyweight):它所代表的对象是不可以共享的,并且可以分解成为多个单纯享元对象的组合。

4、享元工厂角色(FlyweightFactory):负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关键!

5、客户端角色(client):维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。

4、模式解读

享元模式的类图如下所示

 

左半部,和简单工厂模式类似;再看右半部,像合成模式,合成模式用在此处就是为了将具体享元角色和复合享元角色同等对待和处理,通过将享元模式与合成模式组合在一起,可以确保复合享元中所包含的每个单纯享元都具有相同的外蕴状态,而这些单纯享元的内蕴状态往往是不同的。

 /**
 * 复合的享元模式:
 * 复合享元是不能共享的。
 * 但是复合享元是可以分解为可共享的单纯享元。
 */
public class FlyweightTest {
    public static void main(String[] args){
        FlyweightFactory f = new FlyweightFactory();
 
        Flyweight fly = f.factory("aba");
        fly.operation("charles");
 
        f.checkFlyweight();
    }
}
 
/**
 * 抽象享元
 */
abstract class Flyweight{
    public abstract void operation(String extrinsicState);
}
 
 
/**
 * 具体享元
 */
class ConcreteFlyweight extends Flyweight{
 
    private Character inState;
 
    public ConcreteFlyweight(Character inState){
        this.inState = inState;
    }
 
    /**
     * 外蕴状态改变方法行为,但不会改变内蕴状态
     */
    @Override
    public void operation(String extState){
        String str = "inState:" + inState + ";extState:" + extState;
        System.out.println(str);
    }
 
}
 
 
/**
 * 复合享元
 */
class CompositeFlyweight extends Flyweight{
    private Map<Character , Flyweight> map;
 
    public CompositeFlyweight(){
        map = new HashMap<Character, Flyweight>();
    }
 
    public void add(Character c, Flyweight fly){
        map.put(c , fly);
    }
 
    @Override
    public void operation(String extState){
        Iterator<Map.Entry<Character , Flyweight>> it = map.entrySet().iterator();
        while(it.hasNext()) {
            Map.Entry<Character , Flyweight> entry = it.next();
            Flyweight fly = entry.getValue();
            fly.operation(extState);
        }
    }
}
 
 
/**
 * 享元工厂
 */
class FlyweightFactory{
    private Map<Character , Flyweight> map;
 
    public FlyweightFactory(){
        map = new HashMap<Character , Flyweight>();
    }
 
    /**
     * 单纯享元工厂
     */
    public Flyweight factory(Character state) {
 
        Flyweight flyweight;
 
        if(map.containsKey(state)) {
            flyweight = map.get(state);
        } else{
            flyweight = new ConcreteFlyweight(state);
            map.put(state , flyweight);
        }
 
        return flyweight;
    }
 
    /**
     * 复合享元工厂
     * 此处Character的复合类型恰好是String,
     * 当无此巧合时,可使用List等聚集类型传入.
     */
    public Flyweight factory(String compositeState){
        CompositeFlyweight composite = new CompositeFlyweight();
 
        for(int i = 0 ; i < compositeState.length(); i++){
            Character c = new Character(compositeState.charAt(i));
            composite.add(c , this.factory(c));
        }
 
        return composite;
    }
 
    /**
     * 辅助方法
     */
    public void checkFlyweight() {
        Iterator<Map.Entry<Character, Flyweight>> it = map.entrySet().iterator();
        System.out.println("checkFlyweight:");
        while(it.hasNext()) {
            Map.Entry<Character , Flyweight> entry = it.next();
            Character key = entry.getKey();
            System.out.println("key:" + key);
        }
    }
 
}

从复杂度上来讲,复合享元模式显而易见是比单纯享元模式复杂的。再从享元模式的关键——共享,来分析:复合享元模式在共享上面是没有达到预期的效果,可以说是没有起到共享的目的。虽然对于它内部包含的单纯享元角色来说还是能够起到共享的作用,但是复合享元角色中一个内蕴状态和对象使用了空间来保存,肯定不会节省空间和对象个数的。所以复合享元模式是违背享元模式初衷的,因此我们应该尽量使用单纯享元模式。

5JDK涉及到的设计模式

JDK中体现有Integer.valueOf(int
i)
Character.valueOf(char c)以及String常量池。

public static Integer valueOf(String s) throws NumberFormatException {
    return Integer.valueOf(parseInt(s, 10));
}
 
/* i 在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);
}
 
/* IntegerCache,一个内部类,注意它的属性都是定义为static final */
private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];
 
    static {
        /**
         * h值,可以通过设置jdk的AutoBoxCacheMax参数调整,
         * 自动缓存区间设置为[-128,N],注意区间的下界是固定
         */
        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);
                // 数组大小最大为Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
                // 不能解析为int,忽略
            }
        }
        high = h;
 
        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++); //-128到high值逐一分配到缓存数组
 
        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }
 
    private IntegerCache() {}
}

6、模式总结

优缺点

享元模式优点就在于它能够大幅度的降低内存中对象的数量;而为了做到这一步也带来了它的缺点:它使得系统逻辑复杂化,而且在一定程度上外蕴状态影响了系统的速度。

适用场景

1、当我们发现某个类型的对象有大量的实例时,我们是否可以对这些实例进行分类,经过分类后,我们发现只有很少的类别的情况下。

2、我们发现通过使用享元模式后能够提高系统的性能和不会带来更多的复杂度时。

3、享元模式一般是给出本地内存资源节省的一个方案,并不适合互联网上的分布式应用的情况,不过享元模式对于排他性的要求资源的控制,是个不错的选择的。