1 定义
享元模式(Flyweight Pattern)属于结构型设计模式之一,它主要是使用共享对象有效地避免了创建过多的性质相近的对象,从而降低内存的占用,提高程序的性能。它也是池技术的重要实现方式,正如常量池、数据库连接池、缓冲池等都是享元模式的应用。享元模式一般结合工厂模式一起使用,在获取对象时会尝试使用共享的已存在的同类对象,如果未能匹配到对象才再去创建新对象。这些对象分为两个部分:内部状态和 外部状态:
内部状态,是对象内部可共享的信息,它并不会因外部环境改变而发生改变。
外部状态,是对象外部使用的一个标记,它会因外部环境改变而发生改变并且不可共享的状态。
例如现实场景中,某品牌手机有内存128G、256G和512G的版本区分,所以这手机对象有手机型号和内存版本两个字段,其中手机型号就是它的内部状态,而内存版本就是它的外部状态。现在我们需要对这手机进行量产各100台,那么我们当然不需要去重复设计和适配3*100个台,因为其仅不同点只在于内存版本的不同,所以只需要专门的部门研发出3台不同内存版本的样板机,其余的就按思路复制生产即可,这个思路就是享元模式。
2 实现
享元模式一般包含3个角色,分别是:
- 抽象享元(Flyweight),就是具体享元的抽象类,用于定义对象的内部状态和外部状态的接口或者实现。例如上述定义举例的手机统称,它定义出手机型号、手机内存版本等内部和外部状态。
- 具体享元(ConcreteFlyweight),就是实现抽象享元定义的业务。例如上述定义举例的具体的样板手机。
- 享元工厂(FlyweightFactory),就是负责管理对象池和创建享元对象。例如上述定义举例的提供样板机的部门。
抽象享元类,它是手机的统称和手机字段的定义:
public abstract class Phone {
protected String mModel;
protected String mMemory;
protected abstract void showInfo();
}
具体享元,它继承于抽象享元类,是具体的样板手机:
public class ApplePhone extends Phone {
public ApplePhone(String memory) {
mModel = "IPhone99";
mMemory = memory;
}
@Override
protected void showInfo() {
System.out.println(mModel + "很牛逼,内存是:" + mMemory);
}
}
享元工厂,通过一个Map的私有变量来保存已经创建过的对象:
public class PhoneFactory {
private static Map<String, Phone> phonePool = new HashMap<>();
public static Phone getPhone(String memory) {
if (phonePool.containsKey(memory)) {
System.out.println("缓存返回对象,key是:" + memory);
return phonePool.get(memory);
} else {
Phone phone = new ApplePhone(memory);
phonePool.put(memory, phone);
System.out.println("新创建对象,key是:" + memory);
return phone;
}
}
}
客户端:
Phone phone1 = PhoneFactory.getPhone("128G");
phone1.showInfo();
Phone phone2 = PhoneFactory.getPhone("256G");
phone2.showInfo();
Phone phone3 = PhoneFactory.getPhone("512G");
phone3.showInfo();
Phone phone4 = PhoneFactory.getPhone("256G");
phone4.showInfo();
输出结果:
新创建对象,key是:128G
IPhone99很牛逼,内存是:128G
新创建对象,key是:256G
IPhone99很牛逼,内存是:256G
新创建对象,key是:512G
IPhone99很牛逼,内存是:512G
缓存返回对象,key是:256G
IPhone99很牛逼,内存是:256G
3 区别
3.1 与原型模式的区别
享元模式与原型模式非常相似,它们都是为了解决重复创建对象的性能优化方案,我们来聊了儿它们的区别:
- 原型模式是在直接创建对象的代价比克隆更大时通过克隆来解决重复创建的问题;享元模式是在对创建过多的性质相似的对象时通过缓存来解决重复创建的问题。
- 原型模式创建的对象属性完全一样,而享元模式会根据不同的外部状态创建不一样的对象实例。
3.2 与单例模式的区别
享元模式还与懒加载式的单例模式非常相似,它们都是先尝试从内存中获得已创建好的对象,如果未能获取再去创建新对象并进行缓存于内存中,我们又来聊聊它们的区别:
- 单例模式是出于共享对象状态为目的,而享元模式是为了节省内存空间为目的。
- 单例模式是针对一个类的单例,一般会严格控制一个进程只有一个实例,而享元模式不严格控制实例个数,可以针对一个类的不同表现形式的多个单例。
4 总结
既然享元模式是产生对象,为什么不是创建型设计模式而是结构型设计模式呢?因为享元模式是提供了减少对象数量从而改善应用所需的对象结构的方式,所以它被归类到结构型设计模式中。享元模式的初衷是节省内存空间,它对于重复的对象只会被创建一次,适用于需要创建很多性质相近的重复对象的场景。不过要注意的是享元工厂中池容量需要做最值的限制,否则会因为希望节省内存反而让缓存池把内存给占满了。享元模式节省了内存空间,但提高了系统的复杂度,需要明确分离享元业务的外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。