定义
摒弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态,从而让我们能在有限的内存容量中载入更多对象
享元模式要解决的核心问题就是节约内存空间,使用的办法是找出相似对象之间的共有特征,然后复用这些特征
UML图
享元模式包含的关键角色有四个
- 享元类(Flyweight):定义了享元对象需要实现的公共操作方法。在该方法中会使用一个状态作为输入参数,也叫外部状态,由客户端保存,在运行时改变
- 享元工厂类(Flyweight Factory):管理一个享元对象类的缓存池。它会存储享元对象之间需要传递的共有状态,比如,按照大写英文字母来作为状态标识,这种只在享元对象之间传递的方式就叫内部状态。同时,它还提供了一个通用方法
getFlyweight(),主要通过内部状态标识来获取享元对象 - 可共享的具体享元类(ConcreteFlyweight):能够复用享元工厂内部状态并实现享元类公共操作的具体实现类
- 非共享的具体享元类(UnsharedConcreteFlyweight):不复用享元工厂内部状态,但实现享元类的具体实现类, 一般不会出现在享元工厂
简单介绍下内部状态和外部状态
- 内部状态:指对象共享出来的信息, 存储在享元对象内部 且 不会随环境的改变而改变
- 外部状态:随环境改变而改变的状态。通常是某个对象所独有的,不能被共享,因此,也只能由客户端保存。之所以需要外部状态就是因为客户端需要不同的定制化操作
使用场景分析
一般来讲,享元模式常用的使用场景有以下几种。
- 系统中存在大量重复创建的对象。比如,同一个商品的展示图片、详情介绍、文字介绍等,当自营商家系统调用或第三方商家调用时,商品系统可以使用同一个对象来节省内存空间。
- 可以使用外部特定的状态来控制使用的对象。比如,使用常用的中文汉字作为读取的标识,读取享元池中共享的多个中文汉字对象。
- 相关性很高并且可以复用的对象。比如,公司的组织结构人员基本信息、网站的分类信息列表等。
示例代码1
/**
* @description 享元类
*/
public interface Flyweight {
void operation(Integer state);
}
import com.yaron.flyweight.lagou.Flyweight;
import lombok.extern.slf4j.Slf4j;
/**
* @description 共享的具体享元类
*/
@Slf4j
public class ConcreteFlyweight implements Flyweight {
private String uniqueKey;
public ConcreteFlyweight(String uniqueKey) {
this.uniqueKey = uniqueKey;
}
@Override
public void operation(Integer state) {
log.info("====== 享元内部状态: {} 外部状态:{}", uniqueKey, state);
}
}
import com.yaron.flyweight.lagou.Flyweight;
import lombok.extern.slf4j.Slf4j;
/**
* @description 非共享的具体享元类
*/
@Slf4j
public class UnsharedConcreteFlyweight implements Flyweight {
private String uniquekey;
public UnsharedConcreteFlyweight(String uniquekey) {
this.uniquekey = uniquekey;
}
@Override
public void operation(Integer state) {
log.info("====== 使用不共享的对象, 内部状态: {} 外部状态:{}", uniquekey, state);
}
}
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.util.Map;
/**
* @description 享元工厂类
*/
@Slf4j
public class FlyweightFactory {
// 定义一个池 容器
public Map<String,Flyweight> pool;
public FlyweightFactory() {
this.pool = Maps.newLinkedHashMap();
pool.put("A",new ConcreteFlyweight("A")); // 将对应 的内部状态添加进去
pool.put("B",new ConcreteFlyweight("B"));
pool.put("C",new ConcreteFlyweight("C"));
pool.put("D",new ConcreteFlyweight("D"));
}
/**
* 根据内部状态来查找值
*
* @param uniqueKey
* @return
*/
public Flyweight getFlyweight(String uniqueKey){
if (StringUtils.isEmpty(uniqueKey)) return null;
if (pool.containsKey(uniqueKey)){
log.info("==== 享元池中有, 直接复用, key:{}", uniqueKey);
return pool.get(uniqueKey);
}
log.info("==== 享元池中没有, 重新创建并复用,key:{}",uniqueKey);
Flyweight flyweight = new ConcreteFlyweight(uniqueKey);
pool.put(uniqueKey, flyweight);
return flyweight;
}
}
示例代码2
public abstract class WebSite {
public abstract void use(User user);
}
/**
* @Description
*/
public class ConcreteWebSite extends WebSite {
/**
* 网站发布的形式(类型)
* 内部状态
*/
private String type = "";
public ConcreteWebSite(String type) {
this.type = type;
}
/**
*
* @param user 外部状态
*/
@Override
public void use(User user) {
System.out.println("网站发布的形式: " + type+". 使用者是: " + user.getName());
}
}
import com.google.common.collect.Maps;
import java.util.Map;
/**
* @Description
*/
public class WebSiteFactory {
public static WebSiteFactory webSiteFactory;
/**
* 资源池
*/
private Map<String, ConcreteWebSite> pool = Maps.newConcurrentMap();
private WebSiteFactory(){}
public static WebSiteFactory getInstance(){
if (null == webSiteFactory) {
synchronized (WebSiteFactory.class) {
if (null == webSiteFactory) {
webSiteFactory = new WebSiteFactory();
}
}
}
return webSiteFactory;
}
public WebSite getWebSiteCategory(String type) {
if (!pool.containsKey(type)) {
pool.put(type, new ConcreteWebSite(type));
}
return pool.get(type);
}
public int getWebSiteCount() {
return pool.size();
}
}
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @Description 外部状态
*/
@Data
@AllArgsConstructor
public class User {
private String name;
}
/**
* @Description 测试代码
*/
public class Client {
public static void main(String[] args) {
WebSiteFactory factory = WebSiteFactory.getInstance();
WebSite webSite1 = factory.getWebSiteCategory("新闻");
webSite1.use(new User("张三"));
WebSite webSite2 = factory.getWebSiteCategory("博客");
webSite2.use(new User("李四"));
WebSite webSite3 = factory.getWebSiteCategory("博客");
webSite3.use(new User("王五"));
System.out.println(factory.getWebSiteCount());
}
}
享元模式要解决的问题是节约内存的空间大小,而缓存模式本质上是为了节省时间。
回到上面的代码分析中,我们能看出享元模式封装的变化有:
- 对象内部状态的定义规则,比如,是通过字母共享状态,还是通过固定的数字来共享状态;
- 具体享元对象所实现的公共操作的逻辑
所以说,享元模式本质上是通过创建更多的可复用对象的共有特征来尽可能地减少创建重复对象的内存消耗