1 概述
\qquad
运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。
\qquad
享元模式通过数据共享使得重复使用的相同对象在内存中仅创建一次:这就好比汉字中的 “的” 字虽然在文章中反复出现,但它仅在第一次被使用的时候创建 “的” (共享对象)并保存起来,之后再用到 “的” 字的话直接获取之前创建好的 “的” 对象即可,不需要再次创建了。
\qquad
下面是两张使用享元模式前后的对比图,看完这两张图相信大家就能理解享元模式的作用了。
\qquad
使用享元模式前:
\qquad
使用享元模式后:
2 结构
享元(Flyweight )模式中存在以下两种状态:
- 内部状态,即不会随着环境的改变而改变的可共享部分。
- 外部状态,指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。
主要角色有如下:
- 抽象享元角色(IFlyweight): 享元对象抽象基类或接口,同时定义出对象的外部状态和内部状态的接口或实现。
- 具体享元角色(ConcreteFlyweight):实现抽象角色定义的业务。该角色的内部状态处理应该与环境无关,不会出现一个操作改变内部状态,同时修改了外部状态的情况
- 享元工厂(FlyweightFactory):负责管理享元对象池和创建享元对象
- 非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
3 实例
【例】俄罗斯方块
\qquad
下面的图片是众所周知的俄罗斯方块中的一个个方块,如果在俄罗斯方块这个游戏中,每个不同的方块都是一个实例对象,这些对象就要占用很多的内存空间,下面利用享元模式进行实现。
类图
代码
俄罗斯方块有不同的形状,我们可以对这些形状向上抽取出AbstractBox,用来定义共性的属性和行为。
public abstract class AbstractBox {
public abstract String getShape();
public void display(String color) {
System.out.println("方块形状:" + this.getShape() + " 颜色:" + color);
}
}
//接下来就是定义不同的形状了,IBox类、LBox类、OBox类等。
public class IBox extends AbstractBox {
@Override
public String getShape() {
return "I";
}
}
public class LBox extends AbstractBox {
@Override
public String getShape() {
return "L";
}
}
public class OBox extends AbstractBox {
@Override
public String getShape() {
return "O";
}
}
提供了一个工厂类(BoxFactory),用来管理享元对象(也就是AbstractBox子类对象),该工厂类对象只需要一个,所以可以使用单例模式。并给工厂类提供一个获取形状的方法。
public class BoxFactory {
private static HashMap<String, AbstractBox> map;
private BoxFactory() {
map = new HashMap<String, AbstractBox>();
AbstractBox iBox = new IBox();
AbstractBox lBox = new LBox();
AbstractBox oBox = new OBox();
map.put("I", iBox);
map.put("L", lBox);
map.put("O", oBox);
}
public static final BoxFactory getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static final BoxFactory INSTANCE = new BoxFactory();
}
public AbstractBox getBox(String key) {
return map.get(key);
}
}
测试类
public class Client {
public static void main(String[] args) {
// 获取I图形对象
AbstractBox box1 = BoxFactory.getInstance().getShape("I");
box1.display("灰色");
// 获取L图形对象
AbstractBox box2 = BoxFactory.getInstance().getShape("L");
box2.display("绿色");
// 获取O图形对象
AbstractBox box3 = BoxFactory.getInstance().getShape("O");
box3.display("灰色");
// 获取O图形对象
AbstractBox box4 = BoxFactory.getInstance().getShape("O");
box4.display("红色");
System.out.println("两次获取到的O图形对象是否是同一个对象:" + (box3 == box4));
}
}
测试结果
方块形状:I, 颜色:灰色
方块形状:L, 颜色:绿色
方块形状:O, 颜色:灰色
方块形状:O, 颜色:红色
两次获取到的O图形对象是否是同一个对象:true
4 享元模式在Java中的典型应用
\qquad 在Java语言中,String类型就使用了享元模式。String对象是final类型,对象一旦创建就不可改变,同时JAVA会确保一个字符串常量(例如:“Flyweight”)在常量池中只能存在一份拷贝,例如下面这段简单代码:
String str1="Flyweight";
String str2="Flyweight";
if(str1==str2){
System.out.println("str1与str2指向同一对象");
}
else {
System.out.println("str1与str2指向不同对象");
}
打印结果
str1与str2指向同一对象
\qquad 在以上代码中if语句比较的是对象str1和str2中存储的内存地址是否相同,从最终的打印结果可以看出:两次创建的字符串常量"Flyweight"内存地址相同,即两个字符串其实是同一个对象。
5 享元模式的适用性
\qquad Flyweight模式的有效性很大程度上取决于如何使用它以及在何处使用它。当出现下列情形时可以考虑使用Flyweight模式。
\qquad
1)一个应用程序使用了大量的对象。
\qquad
2)完全由于使用大量的对象,造成很大的存储开销。
\qquad
3)对象的大多数状态都可变为外部状态。
\qquad
4)如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。
\qquad
5)应用程序不依赖对象标识。
6 享元模式的特点
享元模式的优点:减少对象数量,节省内存空间。
享元模式的缺点:
\qquad
1)享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。
\qquad
2)享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。
享元模式的本质:分离与共享
7 总结
\qquad 享元模式是一个考虑系统性能的设计模式,通过使用享元模式可以节约内存空间,提高系统的性能。享元模式的核心在于享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,用户需要对象时,首先从享元池中获取,如果享 元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。