概念:
享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。它属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。
享元模式存在两种状态:
内部状态:不会随着环境的改变而改变的的可共享部分
外部状态:指随着环境改变而改变的不可以共享的部分
享元模式的实现要领就是区分应用中的两种状态,将外部状态外部化。
享元模式的角色:
抽象享元角色(Flyweight):是一个接口或者抽象类,在抽象享元类中声明具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法设置外部数据(外部状态)
具体享元角色(Concrete Flyweight):实现了抽象享元类,称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
非享元角色(Unsharable Flyweight):并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类。当需要一个非共享具体享元类的对象时可以直接通过实例化创建
享元工厂角色(Flyweight Factory):负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检查系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,创建一个新的享元对象
实现案例:
以小游戏俄罗斯方块为例,提供抽象享元角色(AbstractBox)、具体享元角色(图形LBox、图形ZBox)、享元工厂角色(BoxFactory)。游戏中,不断的有不同颜色的重复图形出现,这里使用享元模式进行实现。不同颜色为外部状态,参数传递进行实现。
//抽象享元角色
public abstract class AbstractBox {
//获取图形的方法
public abstract String getShape();
//显示图形及颜色
public void display(String color) {
System.out.println("方块形状:" + getShape() + ",颜色:" + color);
}
}
//具体享元角色(图形L)
public class LBox extends AbstractBox {
@Override
public String getShape() {
return "L";
}
}
//具体享元角色(图形Z)
public class ZBox extends AbstractBox {
@Override
public String getShape() {
return "Z";
}
}
//享元工厂 单例
public class BoxFactory {
private Map<String, AbstractBox> map;
private static BoxFactory instance = new BoxFactory();
private BoxFactory() {
map = new HashMap<>();
map.put("L", new LBox());
map.put("Z", new ZBox());
}
public static BoxFactory getInstance() {
return instance;
}
public AbstractBox getShape(String name) {
return map.get(name);
}
}
public class Test {
public static void main(String[] args) {
AbstractBox box1 = BoxFactory.getInstance().getShape("L");
box1.display("绿色");
AbstractBox box2 = BoxFactory.getInstance().getShape("Z");
box2.display("黄色");
AbstractBox box3 = BoxFactory.getInstance().getShape("Z");
box3.display("红色");
//享元模式对图形对象进行了共享
System.out.println(box2==box3);
}
}
输出结果:
从输出结果可以得出box2和box3是同一个对象,享元模式对图像进行了共享,将颜色部分外部化。
享元模式优缺点:
优点:
1、极大减少内存中相似或相同对象数量,节约系统资源,提高系统性能
2、享元模式中的外部状态相对独立,且不影响内部状态
缺点:
为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂
使用场景:
1、一个系统有大量相同或者相似的对象,造成内存的大量耗费
2、对象的大部分状态都可以外部化,可以将这些外部状态传入对象中
3、使用享元模式需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源。因此,应该在需要多次重复使用享元对象时才值得使用享元模式
扩展知识:
享元模式就是所谓的池化的思想,数据库连接池、线程池、JAVA String用的就是享元模式。这里通过源码分析Java Integer类中的享元模式。
Integer i1 = 127;
Integer i2 = 127;
System.out.println(i1==i2);
Integer i3 = 128;
Integer i4 = 128;
System.out.println(i3==i4);
//输出结果分别是:true 和 false
从输出结果可以得知i1和i2是相同对象,i3和i4是两个不同的对象。
关键方法 valueOf(int),Integer默认先创建并缓存 -128 到 127之间的Integer对象。当调用valueOf时,如果参数在-128 到 127之间,则从缓存中读取返回,否则创建新的Integer对象。
关键源码截图如下: