享元模式:实例数量庞大的类需要拆分成变化部分和不变部分
定义:使用共享对象可有效的支持大量的细粒度的对象。
享元模式是池技术的重要实现方式,分配太多的对象到应用程序中会有损程序的性能,还容易造成内存溢出,使用享元模式的思想,细分数据对象,划分数据的内部状态和外部状态,减少创建数据对象的重复数据部分带来的内存损耗。
翻译:
1、程序由数据和算法组成,算法在数量上是固定的,但数据在数量上是无上限的,
2、在数据量十分庞大时(比如双十一的电商购物数据量),数据类全部创建出来会让服务器无法正常运行,
3、要避免创建过多的数据类,就有必要将数据结构中不变的部分作为内部状态抽离出来,将变化的部分作为外部状态与内部状态分离开,并尽量使用简单的数据结构表示它们,让内部状态作为外部状态的附加信息共享,
4、在外部状态有限的情况下,使用工厂模式维护一个数据池,就能实现相同外部条件的数据类的重用,而不用每个数据类都创建实例出来。
内部状态:
对象可以共享出来的信息,存储在享元对象内部,不受环境影响。
例如:
考试成绩单上的ABC评分,优秀,良好,一般,等评语
外部状态:
对象得以依赖的标记,随环境改变,不共享。
例如:
考卷上的分数
类图摘自设计模式之禅:
享元模式中的几个角色:
1、抽象享元角色Flyweight:就是数据的抽象类,同时定义出对象的外部状态和内部状态的接口或实现。
2、具体享元角色ConcreteFlyweight:具体的产品类,实现抽象角色定义的业务,该角色中需要注意内部状态的处理应该与环境无关,不应该出现一个操作即改变外部状态又改变内部状态。
3、不可共享的享元角色unsharedConcreteFlyweight:无外部状态或因线程安全无法使用共享技术的对象,一般不会出现在享元工厂中。
4、享元工厂FlyweightFactory:构造和维护一个池容器,提供从池中获得对象的方法。
享元模式的目的在于运用共享技术,是一些细粒度的对象可以共享,便于重用和重构,防止再进程中创建过多的类的情况出现,
比如全国家长们想要再网上查孩子们的高考成绩,就需要通过共享避免将每一份考卷都new出来,不然服务器肯定遭不住,遭住了也会引起大量的资源浪费而影响其他进程的正常运行。
工厂类中维护一个静态池保存已有的类型,使用现有的stl容器充当池非常合适,本例中使用了vector维护三个考试成绩的等级,outstanding,good,common。
工程池中外部状态的有限使池的大小有限,也就确定了数据类的数量是有限的,不会因需求或调用者的无限增长而增长,使用数据累的共享重用有效避免了创建大量类给进程带来的压力。
享元模式的优点:
大大减少了进程创建的对象,降低程序内存的占用,提升程序性能。
缺点:
固化了外部状态,难以改变,不当修改可能会导致系统的逻辑混乱。
使用场景:
系统中存在大量的相似对象。
细粒度的对象都具备比较接近的外部状态,而且内部状态与环境无关,就是说对象没有特定身份。
需要缓冲池的地方。
问题所在:享元对象数量太少导致的线程不安全,只有通过经验在享元池中一开始就给到足够满足业务要求的享元对象,才能减少线程不安全带来的麻烦,或者干脆从设计上避免出现并发的写操作。
代码示例:通过成绩查看考试结果,并有普通工厂模式和享元模式的区别展示
//享元模式,细粒度化对象的数量,高并发情况下减少内存中同时存在的对象数。
//工厂模式 + 对象池 =》 享元模式
//对象池中的对象只能是只读,对象池解决的也是在只读情况下的对象的对象过多问题
//享元模式在写情况下是线程不安全的,解决方式是人工避免写情况的出现
//比如下面这个例子是讲通过成绩生成特定成绩单的
//包含工厂模式和享元模式的对比和分析
class GradeLevel
{
public:
GradeLevel();
void show();
protected:
string m_strStudyReport;
};
GradeLevel::GradeLevel()
{
cout << "成绩单被构造了" << endl;
}
void GradeLevel::show()
{
cout << m_strStudyReport << endl;
}
class