结构型模式--享元
1、意图
运用共享技术有效地支持大量细粒度的对象。
2、结构
3、参与者
Flyweight:描述一个接口,通过这个接口flyweight可以接受并作用于外部状态。
ConcreteFlyweight:实现Flyweight接口,并为内部状态(如果有的话)增加存储空间。ConcreteFlyweight对象必须是可共享的。它存储的状态必须是内部的;即,它必须独立于ConcreteFlyweight对象的场景。
UnsharedConcreteFlyweight:并非所有的Flyweight子类都需要被共享。Flyweight接口使共享成为可能,但它并不强制共享。在Flyweight对象结构的某些层次,UnsharedConcreteFlyweight对象通常将ConcreteFlyweight对象作为子节点。
FlyweightFactory:创建并管理Flyweight对象;确保合理地共享Flyweight。当用户请求一个Flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(如果不存在的话)。
Client:维持一个对Flyweight的引用;计算或存储一个(多个)Flyweight的外部状态。
4、适用性
享元模式(Flyweight)在以下情况都成立时使用:
一个应用程序使用了大量的对象;
完全由于使用大量的对象,造成很大的存储开销;
对象的大多数状态都可变为外部状态;
如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象;
应用程序不依赖于对象标识。由于享元对象可以被共享,对于概念上明显有别的对象,标识测试将返回真值;
5、代码示例
场景:文本编辑器,若为不同字体的不同字符都创建一个对象的话,将创建大量的对象,造成很大存储开销。通过享元模式,将其中字符作为可共享的对象,字符的字体属性则作为外部状态。
// 图形基类,作为享元模式中的Flyweight class Glyph { public: virtual ~Glyph(); virtual void Draw(Window*,GlyphContext&); virtual void SetFont(Font*,GlyphContext&); virtual Font* GetFont(GlyphContext&); virtual void First(GlyphContext&); virtual void Next(GlyphContext&); virtual bool IsDone(GlyphContext&); virtual Glyph* Current(GlyphContext&); virtual void Insert(Glyph*,GlyphContext&); virtual void Remove(GlyphContext&); protected: Glyph(); }; // Character的子类存储一个字符代码,作为享元模式中的ConcreteFlyweight class Character : public Glyph { public: character(char); virtual void Draw(Window*, GlyphContext&); private: char _charcode; };
// 为了避免给每一个G1yph的字体属性都分配存储空间,我们可以将该属性外部存储于GlyphContext对象中。 // GlyphContext是一个外部状态的存储库,它维持Glyph与字体(以及其他一些可能的图形属性)之间的一种简单映射关系。 // 如果某个操作需要知道在给定场景下Glyph的字体,都会有一个GlyphContext实例作为参数传递给它。 // 然后,该操作就可以查询GlyphContext以获取该场景中的字体信息了。这个场景取决于Glyph结构中的Glyph的位置。 // 因此,当使用Glyph时,Glyph子类的迭代和管理操作必须更新GlyphContext。 // 在遍历过程中,GlyphContext必须知道它在Glyph结构中的当前位置。 // 随着遍历的进行, GlyphContext::Next增加_index的值。 // Glyph的子类(如,Row和Column)对Next操作的实现必须使得它在遍历的每一点都调用GlyphContext::Next。 class GlyphContext { public: GlyphContext (); virtual ~GlyphContext (); virtual void Next(int step = 1); virtual void Insert(int quantity = 1); virtual Font* GetFont(); virtual void SetFont(Font*,int span = 1); private: int _index; // Btree结构存储Glyph到字体的映射。树中的每个节点都标有字符串的长度,而它给这个字符串字体信息。 // 树中的叶子节点指向一种字体,而内部的字符串分成了很多子字符串,每一个对应一种子节点。 BTree* _fonts; };
BTree结构的图示如下:
上图表示,字符1采用Times 24字体,字符2~101(共100个字符)采用Times 12字体,字符102~107采用Times-italic 12字体……以此类推。
// 当字体改变,或者添加删除新的Glyph时,BTree结构将相应地被更新。 GlyphContext gc; Font* times12 = new Font (Times-Roman-12"); Font* timesItalic12 = new Font("Times-Italic-12"); // ... // 修改6个字符的字体,从GlyphContext的_index索引开始算 gc.SetFont(times12, 6); // 添加6个字符 gc.Insert(6); gc.SetFont(timesItalic12, 6);
// GlyphFactory类作为享元模式的FlyweightFactory,负责创建Glypy并进行合理共享。 // GlyphFactory类将实例化Character和其他类型的Glyph。我们只共享Character对象 const int NCHARCODES = 128; class GlyphFactory { public: GlyphFactory(); virtual ~GlyphFactory(); virtual Character* Createcharacter(char); virtual Row* CreateRow(); virtual Column* Createcolumn(); //... private: Character* _character[NCHARCODES]; }; // 初始化为0 GlyphFactory::GlyphFactory () { for (int i=0; i<NCHARCODES; ++i) { _character[i]=0; } } // 如果不存在对应字符,则新建,否则从已有字符组中返回 Character* GlyphFactory::Createcharacter(char c) { if(!_character[c]) { _character[c]= new Character(c); } return _character[c]; } // 其他操作仅需在每次被调用时实例化一个新对象,因为非字符的Glyph不能被共享: Row* GlyphFactory::CreateRow () { return new Row; } Column* GlyphFactory::CreateColumn () { return new Column; }
6、总结
享元模式主要是为避免大量相似对象的开销,减小内存消耗。
享元模式重点在于两个概念:内部状态和外部状态。内部状态存储在Flyweight中,包含Flyweight对象的信息,这些信息可以被共享。外部状态取决于场景,会随场景而变化,不可被共享。外部状态由Client管理和存储,用户操作共享对象Flyweight时,外部状态将传递给共享对象。
当存在需要大量相似对象,而这些相似对象的外部状态又可剥离,剥离后对象数将大大减少的场景需求时,可以用享元模式。
共享对象Flyweight的创建因交由FlyweightFactory管理,才能保证对象能够共享,用户不应该直接创建共享对象。
本文来自博客园,作者:流翎,转载请注明原文链接:https://www.cnblogs.com/hjx168/p/16218565.html