享元模式是结构型模式中最简单的一个模式,享是共享的意思,元是最小单元或细小的对象的意思。也就是说对一些需要大量重复使用的很细的对象进行缓存,缓存了就可以重复使用,例如Integer类中对整型-128到127进行了缓存,使用的正是享元模式。它有点类似单例模式,不过单例模式只共享一个类的唯一对象,而享元模式是共享多个类的唯一对象。
给出定义如下:运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率。用大白话讲就是如果一个项目中有很多很细很小的对象,那么如果对象太多那么会导致JVM内存利用率低下,所以需要对这些很零散的小对象进行统一管理。
享元(Flyweight )模式中存在以下两种状态:
shape
属性就是内部状态,固定死的属性。避免重复创建对象,节省内存空间。根据内部状态把对象存储在共享池,需要时去共享池取就行。内部状态可以理解为对象固有的属性,不能改变,例如俄罗斯方块对象的shape属性,而外部状态是随环境而变化的,例如在不同游戏背景下方块的颜色不一样。因此,对于不变的属性是可以共享的,而变化的属性例如颜色是不可共享的,因为假设现在同时开启了两个不同背景的俄罗斯方块游戏,那么方块的颜色应该是不一样的。
享元模式的主要有以下角色:
俄罗斯方块这个游戏中,需要反复使用到有很多不同的方块(I型,L型,Z型等),每个不同的方块都是一个实例对象,这些对象就要占用很多的内存空间,如果每次出现对象都重新new一个,那么堆上占用的空间就会很多,这里还好俄罗斯方块也就不超过十种,如果是类似于类似于数字游戏的对象呢?每个数字都是一个对象实例,这里仅用俄罗斯方块利用享元模式进行实现。
俄罗斯方块有不同的形状,我们可以对这些形状向上抽取出AbstractBox,用来定义共性的属性和行为。
public abstract class AbstractBox {
private Character shape;
private String color;
public AbstractBox(Character shape){
this.shape = shape;
}
public Character getShape() {
return shape;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "AbstractBox{" +
"shape=" + shape +
", color='" + color + '\'' +
'}';
}
}
接下来就是定义不同的形状了,IBox类、LBox类、ZBox类等。这些类对应着的概念就是元,享元就是共享(缓存)这些频繁创建的对象。
public class IBox extends AbstractBox{
public IBox(){
super('I');
super.setColor("White");
}
}
public class LBox extends AbstractBox{
public LBox(){
super('L');
super.setColor("White");
}
}
public class ZBox extends AbstractBox{
public ZBox(){
super('Z');
super.setColor("White");
}
}
提供了一个工厂类(BoxFactory
),用来管理享元对象(也就是AbstractBox
子类对象),该工厂类对象只需要一个,所以可以使用单例模式。并给工厂类提供一个获取形状的方法。如此的话,需要方块对象只需要从单例工厂中获取即可,所获取到的对象都是缓存的,不会重复创建,节省大量空间。
public class BoxFactory {
private static HashMap<Character,AbstractBox> map;
private BoxFactory(){
map = new HashMap<>();
IBox iBox = new IBox();
LBox lBox = new LBox();
ZBox zBox = new ZBox();
map.put('I',iBox);
map.put('L',lBox);
map.put('Z',zBox);
}
private static class SingletonHolder{
// 静态内部类实现单例模式,如果不懂请学本系列的单例模式
private static final BoxFactory INSTANCE = new BoxFactory();
}
public static final BoxFactory getInstance(){
return SingletonHolder.INSTANCE;
}
public AbstractBox getBox(Character c){
return map.get(c);
}
}
// 客户类,测试调用类
public class Main {
public static void main(String[] args) {
BoxFactory instance = BoxFactory.getInstance();
AbstractBox ibox = instance.getBox('I');
System.out.println(ibox);
System.out.println(instance.getBox('Z'));
System.out.println(instance.getBox('L'));
}
}
为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂。上面的俄罗斯方块的例子只考虑了内部状态,对外部状态没有考虑进去,如果考虑进去将会非常复杂。
JDK例子: Integer
使用了享元模式,默认先创建并缓存 -128 ~ 127
之间数的 Integer
对象,当调用 valueOf
时如果参数在 -128 ~ 127
之间则计算下标并从缓存中返回,否则创建一个新的 Integer
对象。
参考内容:
传智播客系列设计模式笔记
https://zhuanlan.zhihu.com/p/86135908
https://zhuanlan.zhihu.com/p/74872012
https://juejin.cn/post/7088505397639643173
建议阅读下一篇:结构型设计模式——外观模式