C++设计模式13----Flyweight享元模式

Flyweight享元模式概述

作用:运用共享技术有效地支持大量细粒度的对象。

内部状态intrinsic和外部状态extrinsic

1)Flyweight模式中,最重要的是将对象分解成intrinsic和extrinsic两部分。

2)内部状态:在享元对象内部并且不会随环境改变而改变的共享部分,可以称为是享元对象的内部状态

3)外部状态:而随环境改变而改变的,取决于应用环境,或是实时数据,这些不可以共享的东西就是外部状态了。

4)内部状态和外部状态之间的区别:
  在Flyweight模式应用中,通常修改的是外部状态属性,而内部状态属性一般都是用于参考或计算时引用。
Flyweight执行时所需的状态必定是内部的或外部的。内部状态存储于ConcreteFlyweight对象之中;而外部状态则由Client对象存储或计算。当用户调用Flyweight对象的操作时,将该状态传递给它。


    现在让我们通过一个面向对象的文本编辑器设计来说明享元模式的应用。假设我们要设计一个文本编辑器,而且它必须创建字符对象来表示文档中的每个字符,现在让我们考虑字符对象保持什么信息呢?如:字体、字体大小和位置等等信息。

        一个文档通常包含许多字符对象,它们需要大容量的内存。值得我们注意的是一般字符都是由数字、字母和其他字符组成的(它们是固定的,可知的),这些字符对象可以共享字体和字体大小等信息,现在它们专有属性只剩下位置了,每个字符对象只需保持它们在文档中的位置就OK了,通过分析我们已经降低了编辑器的内存需求。

     比如我们有一个含1W个英文字符的文档,如果普通方式保存,我们需要1W个字符对象(每个字符对象包括字符数据,字符大小,字符颜色,字符位置等属性)来分别对应每一个字符的信息,但是如果我们使用享元模式,那么我们只需要把字符进行可共享和不可共享信息的分解,字符数据("A", "B", "C", "D",---)是可以共享的,但是每个字符的大小和颜色等是可以不被共享的。

以文字处理软件为例:

  内部状态存储于flyweight中,它包含了独立于flyweight场景的信息,这些信息使得flyweight可以被共享。如字符数据……

  外部状态取决于flyweight场景,并根据场景而变化,因此不可共享。用户对象负责在必要的时候将外部状态传递给flyweight,如字符大小,字符位置,字符颜色……

享元模式组成

C++设计模式13----Flyweight享元模式_第1张图片

Flyweight  --  抽象享元类

享元类的基类,定义一个接口,通过这个接口Flyweight可以接受并作用于外部状态。

ConcreteFlyweight  --  共享具体享元类

实现Flyweight接口, 并为内部状态( 如果有的话) 增加存储空间。

ConcreteFlyweight对象必须是可共享的。它所存储的状态必须是内部的(intrinsic);即,它必须独立于ConcreteFlyweight对象的场景。

UnsharedConcreteFlyweight  --  复合享元抽象

并非所有的Flyweight子类都需要被共享。Flyweight接口使共享成为可能,但它并不强制共享。在Flyweight对象结构的某些层次,UnsharedConcreteFlyweight对象通常将ConcreteFlyweight对象作为子节点。

FlyweightFactory

1  -- 创建并管理Flyweight对象。本角色必须保证享元对象可以被系统适当地共享

2  --  确保合理地共享Flyweight。当用户请求一个Flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(如果不存在的话),享元工厂角色(Flyweight Factory对象)会检查系统中是否已经有一个符合要求的享元对对象,如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个合适的享元对象。
Client
1)维持一个对Flyweight的引用。

2)计算或存储一个(多个)Flyweight的外部状态

示例代码

#include <iostream>
#include <string>
#include <vector>

using namespace std;

////////////////////////////////////////////////////////////////////////////////
//
//  抽象享元类Flyweight
//
////////////////////////////////////////////////////////////////////////////////
//  Flyweight:享元类的基类,定义一个接口,
//  通过这个接口Flyweight可以接受并作用于外部状态。
//  基类,定义操作接口Operation
class Flyweight
{
public:
    //  构造函数
	Flyweight(std::string intrinsicState)
	{
		this->m_intrinsicState = intrinsicState;
	}

    // 析构函数
	virtual ~Flyweight( )
	{
	
	}
    
    //  [纯虚函数]  --  操作外部状态extrinsicState
    virtual void Operation(const std::string& extrinsicState) = 0;
    
	// 获取内部状态[可以放在ConcreteFlyweight]
	std::string GetIntrinsicState()	
	{
		return this->m_intrinsicState;
	}
   
private:
    //内部状态,也可以放在ConcreteFlyweight中
    std::string m_intrinsicState;
};



////////////////////////////////////////////////////////////////////////////////
//
//  共享享元具体类
//
//////////////////////////////////////////////////////////////////////////////// 
//  ConcreteFlyweight:实现Flyweight接口, 并为内部状态( 如果有的话) 增加存储空间。
//  ConcreteFlyweight对象必须是可共享的。
//  它所存储的状态必须是内部的(intrinsic);即,它必须独立于ConcreteFlyweight对象的场景。
class ConcreteFlyweight : public Flyweight
{
public:
    //  构造函数
	ConcreteFlyweight(std::string intrinsicState)
	:Flyweight(intrinsicState)
	{
	}

	// Îö¹¹º«ËØ
	~ConcreteFlyweight()	
	{	
	}

    //  实现接口函数
	void Operation(const std::string& extrinsicState)
	{
		std::cout << this->GetIntrinsicState() << std::endl;
		std::cout << extrinsicState << std::endl;
	}

};


//  UnsharedConcreteFlyweight:并非所有的Flyweight子类都需要被共享。
//  Flyweight接口使共享成为可能,但它并不强制共享。
//  在Flyweight对象结构的某些层次,UnsharedConcreteFlyweight对象通常将ConcreteFlyweight对象作为子节点。
//  非共享的对象可以直接在客户端中创建使用
class UnsharedConcreteFlyweight : public Flyweight
{
public:
    //  构造函数
	UnsharedConcreteFlyweight(std::string intrinsicState)
	:Flyweight(intrinsicState)
	{
	}

    // 析构函数
	~UnsharedConcreteFlyweight( )
	{
	}

    //  实现接口函数
	void UnsharedConcreteOperation(const std::string& extrinsicState)
	{
		std::cout << extrinsicState << std::endl;
	}
};



//  享元模式工厂类
//  1  --  创建并管理Flyweight对象。
//  2  --  确保合理地共享Flyweight。
//  当用户请求一个Flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(如果不存在的话)
class FlyweightFactory
{
public:
    //  构造函数
	FlyweightFactory( )
	{ 
	}

    // 析构函数
	~FlyweightFactory( )
	{
	}

    //  获得一个请求的Flyweight对象, 如果共享池中存在该对象,则直接返回,否则创建该对象
	Flyweight* GetFlyweight(std::string state)
	{
		vector<Flyweight*>::iterator iter = this->m_vecFly.begin();
		for(;iter!= this->m_vecFly.end();iter++)
		{
            if((*iter)->GetIntrinsicState() == state)           //  如果在容器中找到了需要创建的对象
			{
                return *iter;                               //  直接向对象返回,而不需要重新创建
			}
		}
	
        //  否则, 需要创建的对象还不存在,那么需要创建一个
		Flyweight* fly = new ConcreteFlyweight(state);
    
		this->m_vecFly.push_back(fly);
		return fly;
	}


    // 获取到工厂中,有几个共享的对象
	void GetFlyweightCount()
	{
		std::cout <<"FlyWeight Count : " << this->m_vecFly.size( ) << std::endl;
	}

private:
    //  所有共享对象的组成的共享池,存储了所有被共享的对象,
    //  使用时如果对象在共享池中,直接返回,否则创建该对象,并且将对象加入共享池
    vector<Flyweight*> m_vecFly;
};




// 客户端Client
int main()
{
    //外部状态extrinsicState
    string extrinsicState = "ext";

    //工厂对象,工厂对象
    FlyweightFactory* fc = new FlyweightFactory();

    //向工厂申请一个Flyweight对象,且该对象的内部状态值为“hello”
    Flyweight* fly1 = fc->GetFlyweight("hello");

    Flyweight* fly2 = fc->GetFlyweight("hello");
    Flyweight* fly3 = fc->GetFlyweight("world");
    //应用外部状态
    fly1->Operation(extrinsicState);

    fc->GetFlyweightCount( );

    cout <<(fly1 == fly2) <<endl;               // 共享单元的是使用的是同一个对象,地址是相同的
    return 0;
}



总结

     在享元对象内部并且不会随环境改变而改变的共享部分,可以称之为是享元对象的内部状态,而随环境改变而改变的、不可以共享的状态就是外部状态了。事实上,享元模式可以避免大量非常相似类的开销。在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例除了几个参数外基本上都是相同的,有时就能够受大幅度减少需要实例化的类的数量。如果能把那些参数移到类实例的外面,在方法调用时将它们传递进来,就可以通过共享大幅度地减少单个实例的数目。也就是说,享元模式FlyWeight执行时所需要的状态是有内部的也可能有外部的,内部状态存储于ConcreteFlyWeight对象之中,而外部对象则应该考虑由客户端对象存储或计算,当调用FlyWeight对象的操作时,将该状态传递给它。

       如果一个应用程序使用了大量的对象,而大量的这些对象造成了存储开销时就应该考虑使用;还有就是对象的大多数状态可以外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以考虑用享元模式。


享元模式优点

Flyweight模式的有效性很大程度上取决于如何使用它以及在何处使用它。当以下情况都成立时使用Flyweight模式。

    1) 一个应用程序使用了大量的对象。

    2) 完全由于使用大量的对象,造成很大的存储开销。

    3) 对象的大多数状态都可变为外部状态。

    4) 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。

    5) 应用程序不依赖对象标识。

享元模式缺点


    1)享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。

    2)享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。


享元模式的本质

享元模式的本质:分离与共享

        分离的是对象状态中变与不变的部分,共享的是对象中不变的部分。享元模式的关键之处就在于分离变与不变,把不变的部分作为享元对象的内部状态,而变化部分就作为外部状态,由外部来维护,这样享元对象就能够被共享,从而减少对象数量,并节省大量的内存空间。

       理解了这个本质后,在使用享元模式的时候,就会去考虑,哪些状态需要分离?如何分离?分离后如何处理?哪些需要共享?如何管理共享的对象?外部如何使用共享的享元对象?是否需要不共享的对象?等等问题。

       把这些问题都思考清楚,找到相应的解决方法,那么享元模式也就应用起来了,可能是标准的应用,也可能是变形的应用,但万变不离其宗。

何时选用享元模式

  • 如果一个应用程序使用了大量的细粒度对象,可以使用享元模式来减少对象数量

  • 如果由于使用大量的对象,造成很大的存储开销,可以使用享元模式来减少对象数量,并节约内存

  • 如果对象的大多数状态都可以转变为外部状态,比如通过计算得到,或是从外部传入等,可以使用享元模式来实现内部状态和外部状态的分离

  • 如果不考虑对象的外部状态,可以用相对较少的共享对象取代很多组合对象,可以使用享元模式来共享对象,然后组合对象来使用这些共享对象

相关模式

享元模式与单例模式

    通常情况下,享元模式中的享元工厂可以实现成为单例。另外,享元工厂里面缓存的享元对象,都是单实例的,可以看成是单例模式的一种变形控制,在享元工厂里面来单例享元对象。

享元模式与组合模式

    在享元模式里面,存在不需要共享的享元实现,这些不需要共享的享元通常是对共享的享元对象的组合对象,也就是说,享元模式通常会和组合模式组合使用,来实现更复杂的对象层次结构。

享元模式与状态模式

    可以使用享元模式来共享状态模式中的状态对象,通常在状态模式中,会存在数量很大的、细粒度的状态对象,而且它们基本上都是可以重复使用的,都是用来处理某一个固定的状态的,它们需要的数据通常都是由上下文传入,也就是变化部分都分离出去了,所以可以用享元模式来实现这些状态对象。

享元模式与策略模式

    可以使用享元模式来实现策略模式中的策略对象,跟状态模式一样,在策略模式中也存在大量细粒度的策略对象,它们需要的数据同样是从上下文传入的,所以可以使用享元模式来实现这些策略对象

参考博客

该文以一个文档的实现使用享元模式,接着让我们实现一个享元模式的绘图程序,比较全面

http://www.cnblogs.com/rush/archive/2011/10/01/2197785.html


以一个网站共享的例子使用了享元模式

其中将网站分类作为其内部状态,操作接口为使用者

http://blog.csdn.net/fly_yr/article/details/8577905


跟着CC学设计模式系列--研磨设计模式,比较系统,讲的也很好

http://chjavach.iteye.com/

http://chjavach.iteye.com/blog/1633479

http://sishuok.com/forum/blogPost/list/5639.html


这几个讲的也很不错

http://sourcemaking.com/design_patterns/flyweight/c-sharp-dot-net

http://www.dofactory.com/net/flyweight-design-pattern

http://blog.csdn.net/hguisu/article/details/7535792


你可能感兴趣的:(C++设计模式13----Flyweight享元模式)