设计模式之享元模式

本文详细阐述了享元模式的概念,包括内部状态和外部状态的区别,以及其实现结构。讨论了该模式的优缺点和适用场景,提供了Java代码示例和Integer类的源码分析。

Flyweight design pattern

享元模式的概念、享元模式的结构、享元模式的优缺点、享元模式的使用场景、享元模式的实现示例、享元模式的源码分析


1、享元模式的概念

  享元模式,即运用共享技术来有效的支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量,避免大量相似对象的开销,从而提高系统资源的利用率。

  享元模式中存在以下两种状态:

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

2、享元模式的结构

  • 抽象享元角色:定义向外界提供内部状态的抽象方法,同时声明外界设置外部状态的的方法。
  • 具体享元角色:继承自抽象享元角色,实现向外界提供内部状态的抽象方法。
  • 飞享元角色:即外部状态,即不可共享的部分,可以参数的形式注入到享元对象中去。
  • 享元工厂:负责创建和管理享元对象。当客户对象请求一个享元对象时,享元工厂检查系统中是否已存在符合要求的享元对象,若存在则返回给用户,若不存在则创建一个新的享元对象返回给客户并维护到系统中。因为系统中享元角色的创建和管理只需一个角色,故此工厂可设计位单例。

flyweight-class

3、享元模式的优缺点

  • 优点:
    • 减少相似对象的创建,降低系统内存开销,提高效率。
  • 缺点:
    • 一定程度上增加了系统的复杂度,需要分离出内部状态和外部状态,内部状态不变,外部状态可变,容易造成系统混乱。

4、享元模式的使用场景

  • 当系统中存在大量相同或相似的对象时,避免造成系统内存大量耗费。
  • 当对象的大部分状态可外部化时,可将这些外部状态传入对象中。
  • 在使用享元模式时,需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,故,应在需要多次重复使用享元对象时才值得使用享元模式。

5、享元模式的实现示例

抽象享元角色:

public abstract class Hero {

    /**
     * 向外界提供不可变内部状态(可共享)
     * @return
     */
    public abstract String getName();

    /**
     * 外界设置可变外部状态(不可共享)
     * @param equipment
     */
    public void equipment(String equipment) {
        System.out.println("英雄名称 " + this.getName() + " 装备 " + equipment);
    }
}

具体享元角色:

public class ZedHero extends Hero {

    @Override
    public String getName() {
        return "影流之主";
    }
}

具体享元角色:

public class FizzHero extends Hero {

    @Override
    public String getName() {
        return "潮汐海灵";
    }
}

具体享元角色:

public class AhriHero extends Hero {

    @Override
    public String getName() {
        return "九尾妖狐";
    }
}

享元工厂:

public class FlyWeightFactory {

    private static Map<String, Hero> map;

    private static FlyWeightFactory factory;

    private FlyWeightFactory() {
        map = new HashMap<>(3);
        map.put("zed", new ZedHero());
        map.put("fizz", new FizzHero());
        map.put("ahri", new AhriHero());
    }

    public static FlyWeightFactory getInstance() {
        if (factory == null) {
            synchronized (FlyWeightFactory.class) {
                if (factory == null) {
                    factory = new FlyWeightFactory();
                }
            }
        }
        return factory;
    }

    public Hero getHero(String name) {
        return map.get(name);
    }
}

测试:

public class FlyWeightTest {

    public static void main(String[] args) {
        Hero zed = FlyWeightFactory.getInstance().getHero("zed");
        zed.equipment("德拉克撒的慕刃");

        Hero fizz = FlyWeightFactory.getInstance().getHero("fizz");
        fizz.equipment("卢登的激荡");

        Hero zed1 = FlyWeightFactory.getInstance().getHero("zed");
        zed1.equipment("赛瑞尔达的怨恨");

        System.out.println(zed == zed1);
    }
}

测试结果:

英雄名称 影流之主 装备 德拉克撒的慕刃
英雄名称 潮汐海灵 装备 卢登的激荡
英雄名称 影流之主 装备 赛瑞尔达的怨恨
true

6、享元模式的源码分析

  jdk 的 Integer 类的设计就使用到了享元模式。

public class IntegerTest {

    public static void main(String[] args) {
        Integer a = 127;
        Integer b = 127;

        Integer c = 128;
        Integer d = 128;

        System.out.println(a == b);
        System.out.println(c == d);
    }
}
// 测试结果
true
false

  从测试结果结合 Integer 源码可知,Integer 对 -128 ~ 127 之间的数进行了缓存,所以对象 a、b 实际上是同一个对象,因为其被换存在内存中了,而对象 c、d 则是两个不同的对象,因为其没有被缓存。

  Integer a = 127; 这段代码反编译后实际上会变成 Integer a = Integer.valueOf((int) 127); 也就是实际上是调用了 Integer 的 valueOf() 方法。

@HotSpotIntrinsicCandidate
public static Integer valueOf(int i) {
  if (i >= IntegerCache.low && i <= IntegerCache.high)
    return IntegerCache.cache[i + (-IntegerCache.low)];
  return new Integer(i);
}
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 =
      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() {}
}

  从 Integer.valueOf() 的源码可知,Integer 内部用了一个 IntegerCache 内部类来维护要被缓存的数值范围即被缓存的对象数组,可以看到在 low(-128) 到 high(127)之间的数值被缓存在 cache[] 中了。在调用 Integer.valueOf() 方法时,如果传入的值在 low 和 high 之间,则之间返回数组中的元素,若不在则创建一个新的对象返回。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

红衣女妖仙

行行好,给点吃的吧!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值