一、概述
享元模式:
运用共享技术有效的支持大量细粒度对象的复用
享元模式的定义过于官方,说白了通过池技术来保存大量相似或相同(指同一类型或继承同一父类)的对象,暴露其共同的属性隐藏其私有属性来进行反复的使用。
在享元模式中可以共享出来的部分称为内部状态,而其中不被共享的部分称为外部状态,这样使得这一堆对象即可以使用共同的方法,也可以强制转型后使用自己的特有方法。
在享元模式中我们通常通过创建一个工厂来负责维护我们的享元池,其重要作用就是存储大量具有相同内部状态的享元对象
享元模式被广泛用于池技术上,像我们频繁接触的数据库连接池其实就是享元模式的体现。
二、角色职责与UML
2.1 角色与职责
Flyweight
- 定义一个接口,通过这个接口可以改变享元对象内部的状态
- 定义一些享元对象应该共有的方法
ConcreteFlyweight
- 实现了Flyweight接口,并且具有自己私有的外部状态/属性等
- 可以拥有自己的私有方法,不需要共享给外部
UnsharedConcreteFlyweight
- 一些不需要共享的Flyweight子类
FlyweightFactory
- 享元工厂
- 具有创建和管理享元对象的功能
- 为用户提供共享的享元对象,且用户不可以直接的创建享元对象,必须通过享元工厂
2.2 UML
三、示例
概念很拗口,其实实现很简单,多写几次代码就可以很容易的理解
我们生活中都使用过充电宝吧,毕竟在高使用的情况下手机的电量并不能支持我们连续一整天的使用,而充电宝就显得尤为重要,所以也就衍生出了共享充电宝这个项目,下面我们就拿共享充电宝来举例,完成我们的享元模式代码实验
首先我们创建一个抽象享元类,定义充电宝的内部属性和共有的方法
/**
* @author ZhongJing </p>
* @Description 抽象享元类:定义充电宝 </p>
*/
public abstract class BasePowerBankFlyWeight {
/**
* 维持一个是否在使用中的状态 true:正在使用 false:未使用
*/
boolean inUse = false;
/**
* 使用充电宝
*/
abstract void use();
/**
* 结束使用
*/
abstract void endUse();
}
然后我们定义一个实体类充电宝继承上面的抽象类,
这个类拥有抽象类的共有方法且具有自己的私有属性和特有方法
/**
* @author ZhongJing </p>
* @Description 具体享元类 </p>
*/
public class PowerBank extends BasePowerBankFlyWeight{
/**
* 编号
*/
private Integer id;
/**
* 品牌
*/
private String brand;
public PowerBank(Integer id, String brand) {
this.id = id;
this.brand = brand;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
@Override
void use() {
System.out.println("编号:" + id + " 品牌:" + brand + " 的充电宝正在使用中……");
this.inUse = true;
}
@Override
void endUse() {
System.out.println("编号:" + id + " 品牌:" + brand + " 的充电宝使用结束,已放回……");
this.inUse = false;
}
}
然后我们需要创建一个享元工厂来维护我们所有的享元对象
为了方便,我准备在静态代码块中生成两个享元对象来供我们使用
/**
* @author ZhongJing </p>
* @Description 充电宝箱子:享元工厂 </p>
*/
public class PowerBankBox {
private static Map<Integer, BasePowerBankFlyWeight> powerBankPool = new HashMap<>();
private static Integer powerBankNum = 1;
static {
// 初始化时创建两个充电宝放到充电箱中
PowerBank powerBank = new PowerBank(powerBankNum++, "罗马仕");
PowerBank powerBank2 = new PowerBank(powerBankNum++, "小米");
powerBankPool.put(powerBank.getId(), powerBank);
powerBankPool.put(powerBank2.getId(), powerBank2);
}
/**
* 添加一个充电宝到充电宝箱中
*/
public void addPowerBank(BasePowerBankFlyWeight powerBank) {
powerBankPool.put(powerBankNum++, powerBank);
}
/**
* 取出充电宝
*/
public static BasePowerBankFlyWeight usePowerBank(Integer num) {
// 首先查找指定的充电宝
BasePowerBankFlyWeight powerBank = powerBankPool.get(num);
// 如果找不到指定编号的充电宝或者指定编号充电宝正在使用中,则随机返回一个充电宝
if (Objects.isNull(powerBank) || powerBank.inUse) {
// 遍历连接迟中所有充电宝
for (BasePowerBankFlyWeight p : powerBankPool.values()) {
// 如果某个充电宝未在使用,则返回这个充电宝
if (!p.inUse) {
return p;
}
}
// 循环到最后都没有空闲的充电宝,则返回null
return null;
}
// 如果找到了指定编号的充电宝且没有在使用,直接返回指定充电宝
return powerBank;
}
}
只剩下了测试,我们可以通过享元工厂获取到我们需要的享元类再调用其共有的方法来检测我们的代码
/**
* @author ZhongJing </p>
* @Description 测试类 </p>
*/
public class MainTest {
public static void main(String[] args) {
BasePowerBankFlyWeight powerBank1 = PowerBankBox.usePowerBank(2);
powerBank1.use();
BasePowerBankFlyWeight powerBank2 = PowerBankBox.usePowerBank(null);
powerBank2.use();
powerBank2.endUse();
BasePowerBankFlyWeight powerBank3 = PowerBankBox.usePowerBank(null);
powerBank3.use();
}
}
运行结果如下:
编号:2 品牌:小米 的充电宝正在使用中……
编号:1 品牌:罗马仕 的充电宝正在使用中……
Exception in thread "main" java.lang.NullPointerException
结果可以看到,我们享元池中仅有2个对象,在取出2个对象使用的过程中,如果有另一个客户再去获取对象是取不出来的
这也符合了我们享元的理念,我们囤积大量的享元对象,享元对象一次只能为一人所用,前面的不释放,后面的就拿不到,实现对象利用率的最大化,而不用每次都去创建一个对象,这就是享元模式。