设计模式之享元模式

定义

运用共享技术有效地支持大量细粒度的对象。如围棋中的黑白棋子,教室中的凳子和桌子,这些对象有很多相似的地方,
如果将其中相同的地方提取出来共享,就能节省大量的系统资源,这就是享元模式的适用场景。

结构

  • Flyweight,享元接口,可以接收并作用于外部数据。
  • ConcreteFlyweight,可以共享的享元实现对象,包含自己的内部状态。
  • UnsharedConcreteFlyweight,非共享的享元实现对象,不能共享。
  • FlyweightFactory,享元工厂,用来创建并管理共享的享元对象。

简单实现

享元接口

public interface Flyweight {

  /**
   * 传入外部状态
   */
  void operation(String extrinsicState);
}

可共享的享元实现对象

public class ConcreteFlyweight implements Flyweight {

  /**
   * 内部状态
   */
  private String intrinsicState;

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

  @Override
  public void operation(String extrinsicState) {
    System.out.println("共享对象的内部状态为:" + intrinsicState);
    System.out.println("共享对象的外部状态为:" + extrinsicState);
  }
}

不能共享的享元实现对象

public class UnsharedConcreteFlyweight implements Flyweight {

  @Override
  public void operation(String extrinsicState) {
    System.out.println("非共享对象的外部状态为:" + extrinsicState);
  }
}

享元工厂

import java.util.HashMap;
import java.util.Map;

public class FlyweightFactory {

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

  public Flyweight getFlyweight(String key) {
    if (!flyweightMap.containsKey(key)) {
      flyweightMap.put(key, new ConcreteFlyweight(key));
    }
    return flyweightMap.get(key);
  }
}

客户端

public class Client {

  public static void main(String[] args) {
    FlyweightFactory flyweightFactory = new FlyweightFactory();
    Flyweight flyweight1 = flyweightFactory.getFlyweight("abc");
    Flyweight flyweight2 = flyweightFactory.getFlyweight("abc");
    System.out.println(flyweight1 == flyweight2);
    flyweight1.operation("aaa");
    flyweight1.operation("bbb");
  }

}

在实际开发中,我们可以简化享元模式,不考虑非共享对象。

享元模式在JDK和Spring中的实现

Integer中valueOf()方法

public final class Integer extends Number implements Comparable<Integer> {

    @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会缓存-128到127之间的数

Spring中的ReflectionUtils

public abstract class ReflectionUtils {
    private static Method[] getDeclaredMethods(Class<?> clazz, boolean defensive) {
		Assert.notNull(clazz, "Class must not be null");
		Method[] result = declaredMethodsCache.get(clazz);
		if (result == null) {
			try {
				Method[] declaredMethods = clazz.getDeclaredMethods();
				List<Method> defaultMethods = findConcreteMethodsOnInterfaces(clazz);
				if (defaultMethods != null) {
					result = new Method[declaredMethods.length + defaultMethods.size()];
					System.arraycopy(declaredMethods, 0, result, 0, declaredMethods.length);
					int index = declaredMethods.length;
					for (Method defaultMethod : defaultMethods) {
						result[index] = defaultMethod;
						index++;
					}
				}
				else {
					result = declaredMethods;
				}
				declaredMethodsCache.put(clazz, (result.length == 0 ? EMPTY_METHOD_ARRAY : result));
			}
			catch (Throwable ex) {
				throw new IllegalStateException("Failed to introspect Class [" + clazz.getName() +
						"] from ClassLoader [" + clazz.getClassLoader() + "]", ex);
			}
		}
		return (result.length == 0 || !defensive) ? result : result.clone();
	}
}

缓存一个class的所有方法,Spring中很多地方都使用到了缓存。上述两种示例都是享元模式的变形,只有内部状态,没有外部状态,也就不需要考虑非共享对象了。

总结

优点

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

缺点

  1. 维护共享对象需要额外开销,如考虑共享对象何时失效,何时清除。

本质

享元模式的本质是分离和共享,分离的是对象中变和不变的部分,共享其中不变的部分。

使用场景

  1. 使用了大量的相似的细粒度对象,占用了大量内存。

参考

大战设计模式【19】—— 享元模式
大战设计模式(第二季)【6】———— 从源码看享元模式
设计模式(十二)——享元模式(Integer缓冲池源码分析)
设计模式的征途—12.享元(Flyweight)模式
享元模式(详解版)
研磨设计模式-书籍

posted @ 2021-09-08 21:12  strongmore  阅读(79)  评论(0编辑  收藏  举报