23. 享元模式
定义
享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。
通俗理解
大家都去过图书馆,图书馆里面有书、桌子、电脑,这些东西都是公用的,只需要你注册一个读书证,就能够使用到这些公共资源。那如果这些资源并不是公用的,将会发生什么事情?首先,图书馆要统计某天进入图书馆的人数、他们对桌子、对电脑、对书籍的要求;然后,图书馆要拿着统计的清单,到市场进行采购,运回图书馆提供读者使用;最后,当读者使用过后,把这堆桌子和电脑销毁,烧掉或者回收。
这真是一个土豪的世界。
在编程的时候,也需要考虑到,我们系统的资源并不是无限增长的,总会有一个上限,到达上限之后还继续申请资源,只会发生溢出,整个系统都会挂掉。那么,在new一个对象的时候,是否想到这个对象也占内存?是否知道如果大量的对象堆积的时候,也会造成系统的崩溃?就像图书馆的公用资源一样,如果对每一个读者都定制服务,都购买一张桌子、买一台电脑,那么再多的资本都会白白消耗掉。
对于图书馆来讲,最佳的方式,就是买一批桌子,这一批桌子是大家公用的,这样才能解决公用资源无限增长的问题。对于程序而言,我们创建对象的时候,应该也有这样的实现,如果对象是类似的,那么我们就从公共资源(对象池)里面取,这样才能大大降低系统的资源开销。
示例
业务按照图书馆的桌子来定义。
渣渣程序
每一个用户都new一个对象。
桌子类
public class Desk {
private String str;
public Desk(String str) {
this.str = str;
}
public void info() {
System.out.println("这是一张桌子"+str);
}
}
主入口
public class Main {
public static void main(String[] args) {
Desk desk1 = new Desk("desk1");
Desk desk2 = new Desk("desk2");
// ...
desk1.info();
desk2.info();
//...
}
}
优化
类图
程序
桌子通用抽象类和实现类
public abstract class BaseDesk {
public abstract String getInfo();
public void info() {
System.out.println("桌子信息为:" + this.getInfo());
}
}
public class Desk1 extends BaseDesk {
@Override
public String getInfo() {
return "桌子1";
}
}
public class Desk2 extends BaseDesk {
@Override
public String getInfo() {
return "桌子2";
}
}
桌子工厂
public class DeskFactory {
private static DeskFactory factory = new DeskFactory();
private static Map<String, BaseDesk> publicDesk;
public DeskFactory() {
publicDesk = new HashMap<>(2);
BaseDesk desk1 = new Desk1();
BaseDesk desk2 = new Desk2();
publicDesk.put("desk1", desk1);
publicDesk.put("desk2", desk2);
}
public static DeskFactory getFactory() {
return factory;
}
public static BaseDesk getDesk(String desk) {
return publicDesk.get(desk);
}
}
调用主类
public class Main {
public static void main(String[] args) {
DeskFactory factory = DeskFactory.getFactory();
BaseDesk desk1_1 = factory.getDesk("desk1");
BaseDesk desk1_2 = factory.getDesk("desk1");
System.out.println(desk1_1==desk1_2);//true
desk1_1.info();//桌子信息为:桌子1
desk1_2.info();//桌子信息为:桌子1
}
}
从上面的程序可以知道,最重要的是就是对象池,所有的数据都是从对象池里面获取的。
存在一种情况,桌子都是定制的,张张都一样,如果我需要特殊的桌子,那么要怎么处理呢?其实这就涉及了对象的内部状态和外部状态了,内部状态是指内部的不随着环境改变而改变的状态,例如程序里面的桌子1就是桌子1,不可能变成桌子2;而外部状态是随着环境改变而改变的,例如有个人带了瓶颜料去图书馆,他可以把他那张桌子喷成白色的,程序上只需要在在BaseDesk
相关的代码就可以了。
颜色
public class Color {
private String color;
public Color(String color) {
this.color = color;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
基础桌子类
public abstract class BaseDesk {
//...
public void info(Color color) {
System.out.println("桌子信息为:" + this.getInfo()+",颜色为:"+color.getColor());
}
//...
}
入口
desk1_1.info(new Color("白色"));
desk1_1.info(new Color("黑色"));
优点
- 减少内存中对象的数量,相似相同的对象中内存只有一份,节约系统资源;
- 内外部状态分开,相互不影响。
缺点
- 区分内外部状态,使系统的逻辑复杂;
- 外部状态使得系统的运行时间变长。
应用场景
- 大量的相同或者相似的对象,造成内存的大量消耗;
- 大部分状态都可以外部化的,不适合内部状态容易变动的;
- 池化的技术消耗资源,多次重复使用的享元对象才值得使用享元模式。
实例
JDK String,使用常量池存储
吐槽
就是池化技术,从Map里面获取对象。