【转】设计模式学习笔记(11)-享元

享元(Flyweight)模式,顾名思义即利用共享以支持大量细粒度对象的技术。这里对Flyweight的翻译还是比较文艺的,Flyweight意为轻量的,这个模式就是合理地利用共享对象,使用轻量级的解决方案来解决存储大量的对象的问题。

我们经常会遇到某些拥有大量对象的设计,如文本编辑器,可用的字符不多,汉字也就几千个,但一篇文章往往会超过这个数量级,如果没每个文字都建立一个全新的对象,则会对系统造成巨大的开销,相反如果我们为文章中重复的字符只建立一个副本,所有该文字出现的位置改为对其的引用,这样会大量减少对内存的占用。用类图表示为:

这里,FlyweightFactory是产生Flyweight的一个工厂,client可以通过getFlyweight方法取得,它有一个参数key,因此其中中必然存在类似于Map之类的数据结构存储Flyweight对象。

Flyweight类是一个抽象类,它有两个子类ConcreteFlyweight和UnsharedConcreteFlyweight,分别对应共享的享元对象和不共享的享元对象。因为不是所有的Flyweight子类都必须是共享的,UnsharedConcreteFlyweight类使得共享变得更灵活。

我们下面的代码中将只考虑共享的部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Flyweight
{
 
public:
    virtual void set(string sth) = 0;
    virtual void operation() = 0;
};
class ConcreteFlyweight : public Flyweight
{
private:
    string something;
public:
    void set(string sth)
    {
       something = sth;
    }
    void operation()
    {
        cout << something << endl;
    }
};

这里可以不用Flyweight虚类,因为只有一个子实现,类中有两个方法。set方法用于设置对象的个性信息,Operation则是一些通用操作。关键是实现工厂类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class FlyweightFactory
{
private:
   map<string, Flyweight*> list;
public:
    Flyweight* getFlyweight(string key)
    {
        map<string, Flyweight*>::iterator itr;
        itr = list.find(key);
        if(itr != list.end())
        {
            return itr->second;
        }
        else
        {
            Flyweight* fly = new ConcreteFlyweight;
            list.insert(pair<string, Flyweight*>(key, fly));
            return fly;
        }
    }
};

 

这里使用了map关联结构,其专业术语叫共享池,当客户请求获取对象时,如果池中有这个对象则直接返回,否则new一个对象返回并将该对象放入共享池中。
为了简单化我们并没有使用单例模式,实际编码中最好使用该模式。

在main方法里这样写:

1
2
3
4
5
FlyweightFactory* factory = new FlyweightFactory;
Flyweight* fly = factory->getFlyweight("123");
fly->set("123");
Flyweight* fly2 = factory->getFlyweight("123");
fly2->operation();

为了说明问题我们从factory中取了两次123所对应的对象,并在第一次取出后做了set标记,在第二次取出后我们打印出这个标记:

1
123

结果很显然是刚才设置的,这说明两次取得的对象其实是一个,这便达到了共享的目的。

上面表述的享元的基本结构,然而真正的运用确实不简单的。由于多个相同对象往往并不是完全相同的,这怎么理解呢?我们在文本编辑器中,仅仅有文字是不够的,当然纯文本除外。我们还需要格式控制信息,如字体、颜色等等,相同的字在不同的位置可能并不是完全相同的。怎么办?我们可以将格式信息剥离出来,作为一个外部对象存贮,将外部对象与享元对象结合起来便构成了我们想要的样子。

我们也应该看到享元的不足之处,享元是以时间间换取空间,由于获得对象多了一步,使得计算量加大,因此使用这种模式之前需要进行权衡。
最后来看看享元模式的适用性:

  • 一个应用程序使用了大量的对象
  • 完全由于使用大量对象造成了很大的存储开销
  • 对象的大多数状态都可变为外部状态
  • 如果删除对象的外部状态,那么可以用相对较小的共享对象取代很多的对象
  • 应用程序不依赖于对象标识

享元模式和组合模式经常结合起来使用,同样,在后来要学习的状态模式和策略模式中也十分常见。

posted on 2013-03-05 15:46  TheKingOfKingFish  阅读(130)  评论(0)    收藏  举报

导航