结构型模式--享元

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管理,才能保证对象能够共享,用户不应该直接创建共享对象。

posted @ 2022-05-03 15:55  流翎  阅读(30)  评论(0编辑  收藏  举报