享元对象最重要的就是对“内部状态”和“外部状态”做出了明确的区分,这是享元对象能够被共享的关键所在。
-
内部状态:存储在享元对象内部的,一直不会发生改变的状态,这种状态可以被共享。例如,对于
BlackPiece
类对象,它的内部状态就是黑棋子,它一直代表黑棋子,不会发生改变。而对于WhitePiece
类对象,它的内部状态就是白棋子,它一直代表白棋子,不会发生改变。内部状态一般可以作为享元类的成员变量而存在。 -
外部状态:随着着外部环境和各种动作因素的改变而发生改变的状态,这种状态不可以被共享。例如,黑棋子或者白棋子的位置信息就属于外部状态。当一个享元对象被创建之后,这种外部状态可以在需要的时候传到享元对象内部,例如,在需要绘制棋子的时候,通过调用
BlackPiece
或WhitePiece
类的draw
成员函数并向该成员函数传递位置信息作为参数以达到在不同位置绘制棋子的目的。
这样一来,将内部状态相同的对象保存在享元池中以备随时取出以共享(复用)。当将共享的对象取出后,为其传递不同的外部状态,从而达到生成多个相似对象的效果,而实际上这些相似的对象在内存中只保存一份。在确定享元类的内部状态时,要仔细思考,只将真正需要在多个地方共享的成员变量作为享元对象的内部状态。
千万需要注意的是,不要将享元模式与对象池、连接池、线程池等混为一谈,虽然这几种技术都可以看成是对象的复用,但对象池、连接池、线程池等技术中的“复用”和享元模式中的“复用”并不相同。采用各种池技术时所讲的复用主要目的是节省时间和提高效率,例如,使用完的对象、连接、线程放人到池中而不是通过delete
等释放掉,下次需要创建新对象、连接或线程时可以直接从池中取出来再次使用而不是使用诸如new
等重新创建,但在每一时刻,池中的每个对象、连接、线程都会被一个使用者独占而不会在多处使用,当使用完毕后,再次放回到池中,其他使用者才可以取出来重复使用。而享元模式中的复用指的是享元对象在存在期间被所有使用者共享使用,从而达到节省内存空间的目的。
如果程序中有很多相似对象, 那么将可以节省大量内存。但是可能需要牺牲执行速度来换取内存, 因为他人每次调用享元方法时都需要重新计算部分情景数据。
- 可以使用享元模式实现组合模式树的共享叶节点以节省内存。
- 享元展示了如何生成大量的小型对象, 外观模式则展示了如何用一个对象来代表整个子系统。
- 如果能将对象的所有共享状态简化为一个享元对象, 那么享元就和单例模式类似了。 但这两个模式有两个根本性的不同。
- 只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同。
- 单例对象可以是可变的。 享元对象是不可变的。
总之,设计享元模式时,要重点考虑哪些是对象的内部状态,哪些是对象的外部状态,内部状态固定不变,外部状态可变,通过共享不变的部分达到享元模式的目的一一减少对象数量、节省内存、提高程序运行效率。