前言
享元模式也是一种结构型模式。
享元模式的原理和实现都很简单,但是应用场景却相对狭窄,它和现在我们所熟知的缓存模式、池化模式有所联系,却又有不同。
享元模式强调的是空间效率,比如,一个很大的数据模型对象如何尽量少占用内存并提供可复用的能力;而缓存模式强调的是时间效率,比如,缓存秒杀的活动数据和库存数据等,数据可能会占用很多内存和磁盘空间,但是得保证在大促活动开始时要能及时响应用户的购买需求。也就是说,两者本质上解决的问题类型是不同的。
目录
一、定义
使用共享对象可有效地支持大量地细粒度对象。
摒弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态,从而让我们能在有限的内存容量中载入更多对象。
从这个定义中你可以发现,享元模式要解决的核心问题就是节约内存空间,使用的办法是找出相似对象之间的共有特征,然后复用这些特征。
二、模式原理分析
先了解下内部状态和外部状态的概念
-
内部状态:不会随环境改变而改变的状态,俗称不可变对象。比如,在
Java
中Integer
对象初始化就是缓存 -127 到 128 的值,无论怎么使用Integer
,这些值都不会变化。 -
外部状态:随环境改变而改变的状态。通常是某个对象所独有的,不能被共享,因此,也只能由客户端保存。之所以需要外部状态就是因为客户端需要不同的定制化操作。
//享元类
public interface Flyweight {
void operation(int state);
}//享元工厂类
public class FlyweighFactory {
// 定义一个池容器
public Map<String,Flyweight> pool = new HashMap<>();public FlyweighFactory() { pool.put("A", new ConcreteFlyweight("A"));//将对应的内部状态添加进去 pool.put("B", new ConcreteFlyweight("B")); pool.put("C", new ConcreteFlyweight("C")); } //根据内部状态来查找值 public Flyweight getFlyweight(String key) { if (pool.containsKey(key)) { System.out.println("===享元池中有,直接复用,key:"+key); return pool.get(key); } else { System.out.println("===享元池中没有,重新创建并复用,key:"+key); Flyweight flyweightNew = new ConcreteFlyweight(key); pool.put(key,flyweightNew); return flyweightNew; } } 复制代码
}
//共享的具体享元类
public class ConcreteFlyweight implements Flyweight {
private String uniqueKey;
public ConcreteFlyweight(String key) {
this.uniqueKey = key;
}@Override public void operation(int state) { System.out.printf("=== 享元内部状态:%s,外部状态:%s%n",uniqueKey,state); } 复制代码
}
//非共享的具体享元类
public class UnsharedConcreteFlyweight implements Flyweight {private String uniqueKey; public UnsharedConcreteFlyweight(String key) { this.uniqueKey = key; } @Override public void operation(int state) { System.out.println("=== 使用不共享的对象,内部状态:"+uniqueKey+",外部状态:"+state); } 复制代码
}
就是利用Map
缓存了应该被缓存的类,key
相同就复用对象。
Java
中也有类似操作,比如,在 Java
中 Integer
对象初始化就是缓存 -127 到 128 的值,无论怎么使用 Integer
,这些值都不会变化。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
复制代码
三、使用场景
-
系统中存在大量重复创建的对象。比如,同一个商品的展示图片、详情介绍、文字介绍等
-
相关性很高并且可以复用的对象。比如,公司的组织结构人员基本信息、网站的分类信息
-
需要缓冲池的场景
四、优点
-
减少内存消耗,节省服务器成本
-
聚合同一类的不可变对象,提高对象复用性
五、缺点
- 以时间换空间,间接增加了系统的实现复杂度,提高了系统的复杂性,需要分离出外部状态和内部状态,当使用自己编写的类作为外部状态时,必须重写
equals
和hashCode
方法,所以外部状态最好以Java
基本类型为标志