设计模式之享元模式

享元模式的定义和结构

享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用,享元模式是构造型模式之一,它通过共享数据使得相同对象在内存中仅创建一个实例,以降低系统创建对象实例的性能消耗。
如JDK中的String的常量池,就是使用的享元模式。

public static void main(String[] args) throws UnknownHostException {
    String a = "abc";
    String b = "abc";
    System.out.println(a == b);
}

结果如下:

true

当第一次创建abc时会在字符串常量池中常见一个“abc”的字符串,当再次使用时,直接将引用指向原先的字符串。同时享元模式是将变与不变分离,同时共享不变的内容。所以享元模式就有两种状态:内部状态和外部状态。

  • 内部状态:即不会随着环境的改变而改变的可共享部分。
  • 外部状态:指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。

怎么理解这个内部状态和外部状态呢?举个例子:

在这里插入图片描述
如上图所示,正常的话,如果使用代码来实现,我们是定义一个Circle类,其中定义颜色、坐标、直径等信息。由于存在对象复用的情况,所以我们使用享元模式,那么就需要考虑,那些是不变的,也就是通用的,那些是变的。可以看到颜色、直径是不变的,可以作为其内部装填,而坐标是可变的,取决于客户端调用时放到哪个坐标上,所以这里的颜色和直径就变为内部状态,而坐标作为外部状态。
那么享元模式的结构如下:
在这里插入图片描述
享元模式中包含以下几个角色:

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

享元模式的实现

抽象享元角色和具体享元:

interface Flyweight{

    void operation(String extrinsicState);
}

class ConcreteFlyweight implements Flyweight{

    private String intrinsicState;

    ConcreteFlyweight(String intrinsicState) {
        this.intrinsicState = intrinsicState;
    }

    public void operation(String extrinsicState) {
        System.out.println("extrinsicState:" + extrinsicState);
        System.out.println("intrinsicState:" + intrinsicState);
    }
}

享元工厂:(享元工厂FlyweightFactory负责维护一个享元对象存储池(Flyweight Pool)来存放内部状态的对象。为了调用方便,该工厂类一般使用单例模式来实现。)

class FlyweightFactory {

    private static FlyweightFactory instance = new FlyweightFactory();

    private Map<String, Flyweight> flyweightMap = new HashMap<String, Flyweight>();

    private FlyweightFactory() {

    }

    public static FlyweightFactory getInstance() {
        return instance;
    }

    public Flyweight getFlyweight(String intrinsicState) {
        Flyweight flyweight = flyweightMap.get(intrinsicState);
        if (flyweight == null) {
            flyweight = new ConcreteFlyweight(intrinsicState);
            flyweightMap.put(intrinsicState, flyweight);
        }
        return flyweight;
    }

    public Map<String, Flyweight> getFlyweightMap() {
        return flyweightMap;
    }
}

享元模式的适用性及优缺点

享元模式的适用性:

  • 一个应用程序使用了大量的对象。
  • 完全由于使用大量的对象,造成很大的存储开销。
  • 对象的大多数状态都可变为外部状态。
  • 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。
  • 应用程序不依赖对象标识。

享元模式的优点:

  • 减少对象数量,节省内存空间。

享元模式的缺点:

  • 享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。
  • 享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长(我理解的是由于将对象的状态分为内部状态和外部状态,获取到对象后可以直接通过对象获取其内部状态,但是外部状态需要通过计算或者其他方式获取,那么不能直接通过共享对象直接获取)。

JDK中享元模式的使用

String中享元模式的使用

由于字符串是创建频率且复用频率较高,所以JVM中为字符串分配了字符串常量池,针对没有创建字符串的数据在字符串常量池中分配内存并创建,在后续再次使用时,直接将引用指向已创建的对象从而实现共享。

Short、Character、Integer和Long等使用享元模式减少对象的创建

在Short、Character、Integer中使用了享元模式,这里以Integer的valueOf方法来看,首先看下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.low,IntegerCache.high]之间则是直接去IntegerCache的cache数组中获取,那么来看一下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() {}
}

可以看到,在IntegerCache中有一个cache数组,在IntegerCache类初始化时,将指定范围的数值创建Integer实例,并放入数组中,默认范围为[-128,127]。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值