享元设计模式 Flyweight
根据案例分析传统模式中可能存在的问题
案例: 生成合同: 不同用户生成不同类型合同,根据合同类型,生成对应的 world 合同模板,获取用户数据填充进合同模板
传统模式: 每个用户生成合同,都会去执行一次生成world合同模板的操作,然后去获取用户信息填充模板
可能存在的问题: 假设在生成world时,比浪费资源,而用户需要生成的合同类型是规定的几种,考虑用户在第一次生成合同时,将该合同存起来,下次或下个用户再次生成相同类型的合同直接去获取即可
享元模式中主要关注的问题
- 内部状态: 相同部分,例如在生成合同时,相同类型的合同逻辑是相同的,合同模板是相同的
- 外部状态: 不同部分,例如生成合同时填充合同的客户信息就是外部状态,不同客户直接是不同的
- 分析出内部状态数据与外部状态数据,内部状态数据通过内部封装进行保存,外部状态数据通过外部传递,然后封装,进行实际操作
- 实际操作: 例如此处,获取用户信息- - ->获取合同数据,填充合同模板的生成实际合同的操作,封装数据,实际执行
- 池: 存放内部状态数据的容器,例如用户需要生成某个类型的合同,首先去池中获取,有直接返回,否则生成该类型的合同,存入池中然后返回
代码示例
- 分析哪些数据是内部状态,哪些数据是外部状态剥离,生成合同到world是内部状态,不同用户生成的相同类型的合同是相同的,用户信息是外部状态,不同用户用户名不同
- 创建抽象父接口,接口中定义生成合同的抽象方法,在生成合同时需要通过该方法,将外部状态数据用户信息传递给内部状态数据未填充用户信息的合同数据
//抽象父接口
abstract class AbsContract{
//外部状态数据通过该方法与内部状态数据进行交互
public abstract void use(User user);
}
- 创建内部状态类,也就是可以共享的合同,该类继承AbsContract父类,并抽象抽象方法,通过方法形参获取到外部状态数据
//具体的合同
class Contract extends AbsContract{
//合同类型(共享的部分,属于内部状态)
private String contractType;
//提供带参构造,初始化合同类型这个步骤可以看为是组装合同模板的逻辑代码
public Contract(String contractType) {
this.contractType = contractType;
}
//生成合同,填充用户信息
@Override
public void use(User user) {
System.out.println("客户"+ user.getName() +"生成合同为:" + contractType);
}
}
- 创建非共享数据的外部状态也就是用户信息类
//外部状态
class User{
//不共享的数据
private String name;
//通过构造器赋值的过程可以看为是封装外部状态数据
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
- 创建工厂类,定义存放内部状态数据的池,此处使用HashMap,提供在池中获取共享数据的方法,若池中没有指定获取的数据,则创建一个放入池中
class ContractFactory{
//充当池的作用,将创建好的内部状态对象数据存放在该池中
//当需要时在池中获取即可
private HashMap<String,Contract> contractMap = new HashMap<>();
//获取数据对象,如果有则返回,如果没有创建,存入池中,然后返回
//根据合同类型获取合同,如果有则返回,如果没有则创建合同存入池中
public AbsContract getContract(String contractType) {
if(! contractMap.containsKey(contractType)) {
//创建合同可以看为是封装共享数据的逻辑代码
Contract contract = new Contract(contractType);
//将封装好的共享数据存入池中
contractMap.put(contractType, contract);
}
return contractMap.get(contractType);
}
//获取池中的合同个数
public void getSize() {
System.out.println(contractMap.size());
}
}
- 调用测试
public class Test {
public static void main(String[]args) {
//1.创建工厂对象
ContractFactory factory = new ContractFactory();
//2.创建用户信息对象(不共享数据)
User user = new User("小明");
//3.获取合同(共享数据)
AbsContract contract1 = factory.getContract("抵押合同");
//4.调用use()方法生成合同,
contract1.use(user);
User user2 = new User("小红");
AbsContract contract2 = factory.getContract("放款合同");
//user2生成合同
contract2.use(user2);
//再次获取"放款合同" 在第一次获取是池中已经存放了一份,此处直接返回即可不用再次创建
AbsContract contract3 = factory.getContract("放款合同");
factory.getSize();//输出2,两次"放款合同"池中只有一份
}
}
- 流程图(不是UML)
根据实际案例分析享元模式的角色及优点
- 抽象享元类,也就是此处的AbsContract, 具体享元类的抽象类或接口,通过这个接口可以拿到外部状态数据
- 具体享元类,也就是此处的Contract, 内部状态数据类,在该类中封装内部状态数据
- 非共享具体享元类,也就是此处的User,不需要共享外部状态数据类,通过该类封装出外部数据
- 享元工厂类,也就是此处的ContractFactory,提供存放具体享元类Contract的池,返回需要的具体享元类,或新建一个(不存在的话)
- 优点: 享元模式可以减少系统中对象的数量,减少某些对象的重复创建代理的资源浪费,核心在于享元工厂,确保合理的共享对象数据
- 自己理解: 在实现某个功能时,分析出相同数据与不同数据,剥离,将相同数据存放起来,不同数据通过方法进行传递,相同数据与不同数据通过方法进行交互封装,然后执行实际功能
JDK 中享元模式的应用案例
分析下面代码,通过valueOf()方法获取的Integer判断是否是一个对象返回true
Integer i = Integer.valueOf(11);
Integer x = Integer.valueOf(11);
System.out.println(i==x); //true
查看JDK中的Integer的valueOf()源码发现,当传递给valueOf()方法的值大于等于IntegerCache.low 并且小于等于IntegerCache.high时,会在IntegerCache.cache中直接获取,low是-128, high是127,此处的IntegerCache.cache是一个池子,当在范围中时,就是享元模式,直接在池中获取
/**
* Returns an {@code Integer} instance representing the specified
* {@code int} value. If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since 1.5
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}