对象的复用——享元模式

作者: RunAlgorithm | 来源:发表于2019-05-14 08:15 被阅读0次

    1. 定义

    享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。

    共享对象,复用对象实例。

    对象复用

    2. 设计

    主要角色:

    • 抽象享元类(Flyweight),声明享元公共方法,可以给外界获取内部状态,或者给享元设置外部状态。
    • 具体享元类(Concrete Flyweight),实现享元抽象类,为内部享元提供存储空间。
    • 非共享享元类(Unshared Concrete Flyweight),不能被共享的享元类。
    • 享元工厂类(Flyweight Factory),创建并且管理享元对象。

    类图:

    享元模式-类图

    抽象享元类,定义所有享元的公共方法。

    public interface IFlyweight {
    
        void sayHello();
    }
    

    具体享元类,实现方法:

    public class ConcreteFlyweight implements IFlyweight {
    
        private String hello;
    
        public ConcreteFlyweight(String hello) {
            this.hello = hello;
        }
    
        public void sayHello() {
            System.out.println(hello);
        }
    }
    

    享元工厂,使用 Map 缓存享元,在创建享元的接口里,先走缓存获取,没有的话再创建。

    public class FlyweightFactory {
    
        private Map<String, IFlyweight> flyweightMap = new HashMap<String, IFlyweight>();
    
        public IFlyweight getFlyweight(String key) {
            IFlyweight flyweight = flyweightMap.get(key);
            if (flyweight == null) {
                flyweight = new ConcreteFlyweight(key);
                flyweightMap.put(key, flyweight);
            }
            return flyweight;
        }
    
    }
    

    使用地方:

    public class TestFlyweight {
    
        public static void main(String[] args) {
            FlyweightFactory factory = new FlyweightFactory();
    
            // 新对象
            IFlyweight flyweight = factory.getFlyweight("sayHello");
            flyweight.sayHello();
    
            // 新对象
            IFlyweight flyweight1 = factory.getFlyweight("你好");
            flyweight1.sayHello();
    
            // 对象复用
            IFlyweight flyweight2 = factory.getFlyweight("sayHello");
            flyweight2.sayHello();
            System.out.println("复用:" + (flyweight == flyweight2));
    
            // 不可复用对象
            IFlyweight flyweight3 = new UnshareConcreteFlyweight();
            flyweight3.sayHello();
        }
    }
    

    可以看到,后续一样的 "sayHello" 对象没有重复创建,而是复用了之前创建的。

    2.1. 内部状态与外部状态

    享元类设计的关键步骤就是把内部状态和外部状态进行剥离。

    • 内部状态可以做为成员变量。
    • 外部状态不可以,需要外部传入。

    3. 应用

    应用在这些场景:

    • 节约内存,存在大量相同或者相似的对象。
    • 对象需要重复使用。

    3.1. DruidDataSource

    DruidDataSource 属于数据库连接池,用来缓存数据库连接。

    数据库连接可复用,无需大量创建。

    缓存池使用了数组实现,连接和连接相关的配置信息被封装在 DruidConnectionHolder 中了。

    private volatile DruidConnectionHolder[] connections;
    

    具体的获取逻辑在 getConnectionInternal 中实现。先从缓存读取,没有的话阻塞等待新创建的连接然后返回。

    3.2. JDK:Integer#valueOf(int)

    Java 使用 Integer 包装类的时候,使用工厂方法 valueOf 的地方有应用了享元模式。

        public static Integer valueOf(int i) {
            if (i >= IntegerCache.low && i <= IntegerCache.high)
                return IntegerCache.cache[i + (-IntegerCache.low)];
            return new Integer(i);
        }
    

    可以内部使用了 IntegerCache 来进行缓存。

        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() {}
        }
    

    可以看到,在 [-128,127] 的包装类已经在类加载过程中,提前放入了缓存中。

    这个区间的包装类使用频繁,这里可以进行复用。

    3.3. Glide:Bitmap 缓存

    4. 特点

    4.1. 优点

    • 节约内存,相同或者相似的对象只留一份。
    • 外部状态独立,独立的外部状态,在不同的环境中均可以使用。

    4.2. 缺点

    • 设计复杂,享元需要分离出外部状态和内部状态。
    • 外部状态获取耗时,获取外部状态时间较长。

    相关文章

      网友评论

        本文标题:对象的复用——享元模式

        本文链接:https://www.haomeiwen.com/subject/biljaqtx.html