从开源框架理解设计模式系列#FlyWeight享元模式

享元模式是一种用于高效地支持大量相似对象的技术,通过共享对象来减少内存消耗。在Java中,Integer类的缓存机制是享元模式的经典应用。Guava的缓存也采用了享元模式,实现自动过期和数据懒加载。享元模式适用于存在大量重复对象的场景,但可能会牺牲对象的独立性。
摘要由CSDN通过智能技术生成

目录

what什么是享元模式

why为什么需要享元模式

how如何实现享元模式

开源框架经典案例

JDK源码之Integer类

Guava缓存

使用场景

优缺点对比

优点

缺点

参考资料


what什么是享元模式

        Gof定义:运用共享技术有效地支持大量细粒度的对象。

        HeadFirst定义:如何想让某个类的一个实例能用来提供许多“虚拟实例”,就使用享元模式。

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

        FlyWeight通常和Composite模式一起使用,用共享叶节点的DAG图来实现逻辑上的层次。

why为什么需要享元模式

        有些应用程序得益于其在整个设计过程中采用对象技术,但简单化的实现代价极大。

        例如,大多数文档编辑器都使用对象来表示嵌入的成分,例如表格和图形。通常文档编辑器会使用对象来表示内容,但这种设计的缺点在于代价很大,即使是一个中等大小的文档也需要几千个字符,这样会耗费大量内存。所以通常不受每个字符都用一个对象来表示。Flyweight的模式描述了如何共享对象,这样使得可以细粒度的使用它们。

        说的有点抽象,这里我们想一下jvm常量池里面的对象,应该就比较明白。面向对象技术可以很好地解决一些灵活性或可扩展性问题,但在很多情况下需要在系统中增加类和对象的个数。当对象数量太多时,将导致运行代价过高,带来性能下降等问题。

      

how如何实现享元模式

        享元模式的主要角色有如下。

  1. 抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
  2. 具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
  3. 非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
  4. 享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。


        下图是享元模式的结构图,其中:

  • UnsharedConcreteFlyweight 是非享元角色,里面包含了非共享的外部状态信息 info;
  • Flyweight 是抽象享元角色,里面包含了享元方法 operation(UnsharedConcreteFlyweight state),非享元的外部状态以参数的形式通过该方法传入;
  • ConcreteFlyweight 是具体享元角色,包含了关键字 key,它实现了抽象享元接口;
  • FlyweightFactory 是享元工厂角色,它是关键字 key 来管理具体享元;
  • 客户角色通过享元工厂获取具体享元,并访问具体享元的相关方法。

享元模式的结构图

开源框架经典案例

JDK源码之Integer类

        比较经典的比如Integer类,很多同学在面试过程中会被问到,Integer可不可以用==号进行判断,这个面试官就是想考你知不知道其内部实现,只要看过源码就知道,Integer.valueOf()这个静态工厂方法Integer实例时,当传入的int范围在-128~+127之间时,会直接返回缓存的Integer实例,因为反复创建同一个值相同的包装类型是没有必要的。而为什么是-128到+127,这里面缓存范围,可以通过XX:AutoBoxCacheMax改变最大值,但是不能改变最小值。

/**
     缓存范围,可以通过XX:AutoBoxCacheMax改变最大值,
     * 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 integerCacheHighPropV    alue =
                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() {}
    }

Guava缓存

        熟悉guava的同学应该知道,guava有个AbstractLoadingCache类专门用来做缓存,同时支持自动过期清理,数据懒加载等等功能,很强大,因此其核心设计也是该设计模式。

        看一下这个类的源码吧,其实核心方法很简单,首先通过CacheBuilder的build()方法加载缓存,再通过LoadingCachegetUnchecked便是去获取Key。哦,这里又有Builder模式了。



@Beta
public abstract class AbstractLoadingCache<K, V> extends AbstractCache<K, V> implements LoadingCache<K, V> {
    protected AbstractLoadingCache() {
    }

    public V getUnchecked(K key) {
        try {
            return this.get(key);
        } catch (ExecutionException var3) {
            throw new UncheckedExecutionException(var3.getCause());
        }
    }

    public ImmutableMap<K, V> getAll(Iterable<? extends K> keys) throws ExecutionException {
        Map<K, V> result = Maps.newLinkedHashMap();
        Iterator i$ = keys.iterator();

        while(i$.hasNext()) {
            K key = i$.next();
            if (!result.containsKey(key)) {
                result.put(key, this.get(key));
            }
        }

        return ImmutableMap.copyOf(result);
    }

    public final V apply(K key) {
        return this.getUnchecked(key);
    }

    public void refresh(K key) {
        throw new UnsupportedOperationException();
    }
}

使用场景

  • 当一个系统里面有大量相同或者相似的额对象,由于这类对象的大量使用,造成内存的浪费可以使用
  • 需要享元模式会新增一个共享对象池,而这需要耗费资源,因此只有重复使用时才能使用。
  • 当一个类的许多实例可以被同一个方法控制的时候,就可以使用。

优缺点对比

优点

  • 减少了运行时对象实例的个数,节省内存。
  • 将许多虚拟对象集中管理。

缺点

  • 一旦实现了,那么单个的逻辑实例无法拥有独立的行文,毕竟是共享的嘛。

参考资料

设计模式之禅

Gof设计模式:可复用面向对象软件的基础 典藏版

Head_First设计模式

为什么强烈推荐 Java 程序员使用 Google Guava 编程!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值