C++设计模式之享元模式

来源:互联网 发布:云计算三种模式的区别 编辑:程序博客网 时间:2024/06/05 23:46

概述

想想我们编辑文档用的wps,文档里文字很多都是重复的,我们不可能为每一个出现的汉字都创建独立的空间,这样代价太大,最好的办法就是共享其中相同的部分,使得需要创建的对象降到最小,这个就是享元模式的核心,即运用共享技术有效地支持大量细粒度的对象。

享元对象能做到共享的关键是区分内蕴状态(Internal State)和外蕴状态(External State)。内蕴状态是存储在享元对象内部并且不会随环境改变而改变。因此内蕴状态并可以共享。一般在对象创建的时候传入,之后不做修改

外蕴状态是随环境改变而改变的、不可以共享的状态。享元对象的外蕴状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部(一般都是由参数的方式进行传入,并且对象不会进行对该状态存储,利用原有的内部状态和新传入的外部状态立即实现相应的业务逻辑后就会废弃刚刚获得的外部装填)。外蕴状态与内蕴状态是相互独立的。

类图与样例


抽象享元类(Flyweight

它是所有具体享元类的超类。为这些类规定出需要实现的公共接口,那些需要外蕴状态(Exte的操作可以通过方法的参数传入。抽象享元的接口使得享元变得可能,但是并不强制子类实行共享,因此并非所有的享元对象都是可以共享的。

具体享元类(ConcreteFlyweight)

具体享元类实现了抽象享元类所规定的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享。有时候具体享元类又称为单纯具体享元类,因为复合享元类是由单纯具体享元角色通过复合而成的。

不能共享的具体享元类(UnsharableFlyweight)

不能共享的享元类,又叫做复合享元类。一个复合享元对象是由多个单享元对象组成,这些组成的对象是可以共享的,但是复合享元类本身并不能共享。

享元工厂类(FlyweightFactoiy)

享元工厂类负责创建和管理享元对象。当一个客户端对象请求一个享元对象的时候,享元工厂需要检查系统中是否已经有一个符合要求的享元对象,如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有适当的享元对象的话,享元工厂角色就应当创建一个新的合适的享元对象。

客户类(Client)

客户类需要自行存储所有享元对象的外蕴状态。


[cpp] view plain copy
  1. // CplusplusFlyweight.cpp : Defines the entry point for the console application.  
  2. //  
  3.   
  4. #include "stdafx.h"  
  5. #include <iostream>  
  6. #include <map>  
  7. using namespace std;  
  8. class Character    
  9. {  
  10. public:  
  11.     virtual ~Character(){};  
  12.   
  13.     virtual void SetSize(intint) = 0;  
  14.     virtual void Display() = 0;  
  15. protected:  
  16.     Character(){};  
  17.     char m_chSymbol;  
  18.     int m_nWeight;  
  19.     int m_nHeight;  
  20. };  
  21.   
  22. class CharacterA : public Character  
  23. {  
  24. public:  
  25.     CharacterA();  
  26.     virtual ~CharacterA();  
  27.   
  28.     void SetSize(intint);  
  29.     void Display();  
  30. };  
  31.   
  32. CharacterA::CharacterA()  
  33. {  
  34.     this->m_chSymbol = 'A';  
  35.     this->m_nWeight = 100;  
  36.     this->m_nHeight = 200;  
  37. }  
  38.   
  39. CharacterA::~CharacterA()  
  40. {  
  41.   
  42. }  
  43. void CharacterA::SetSize(int nWeight, int nHeight)  
  44. {  
  45.     this->m_nWeight = nWeight;  
  46.     this->m_nHeight = nHeight;  
  47. }  
  48. void CharacterA::Display()  
  49. {  
  50.     cout << "CharacterA:" << m_chSymbol << "(" << m_nWeight << "," << m_nHeight << ")" << endl;  
  51. }  
  52.   
  53. class CharacterB : public Character  
  54. {  
  55. public:  
  56.     CharacterB();  
  57.     virtual ~CharacterB();  
  58.   
  59.     void SetSize(intint);  
  60.     void Display();  
  61. };  
  62.   
  63. CharacterB::CharacterB()  
  64. {  
  65.     this->m_chSymbol = 'B';  
  66.     this->m_nWeight = 100;  
  67.     this->m_nHeight = 200;  
  68. }  
  69.   
  70. CharacterB::~CharacterB()  
  71. {  
  72.   
  73. }  
  74.   
  75. void CharacterB::SetSize(int nWeight, int nHeight)  
  76. {  
  77.     this->m_nWeight = nWeight;  
  78.     this->m_nHeight = nHeight;  
  79. }  
  80.   
  81. void CharacterB::Display()  
  82. {  
  83.     cout << "CharacterB:" << m_chSymbol << "(" << m_nWeight << "," << m_nHeight << ")" << endl;  
  84. }  
  85.   
  86. class CharacterFactory    
  87. {  
  88. public:  
  89.     CharacterFactory();  
  90.     virtual ~CharacterFactory();  
  91.   
  92.     Character* GetCharacter(char);  
  93. private:  
  94.     std::map<char, Character*> m_mChar;  
  95. };  
  96.   
  97. CharacterFactory::CharacterFactory()  
  98. {  
  99.     m_mChar.insert(make_pair<char, Character*>('A'new CharacterA));  
  100.     m_mChar.insert(make_pair<char, Character*>('B'new CharacterB));  
  101. }  
  102.   
  103. CharacterFactory::~CharacterFactory()  
  104. {  
  105.   
  106. }  
  107.   
  108. Character* CharacterFactory::GetCharacter(char chIn)  
  109. {  
  110.     map<char, Character*>::iterator it = m_mChar.find(chIn);  
  111.     if(it != m_mChar.end())  
  112.     {  
  113.         return (Character*)it->second;  
  114.     }  
  115.   
  116.     return NULL;  
  117. }  
  118.   
  119. int _tmain(int argc, _TCHAR* argv[])  
  120. {  
  121.     CharacterFactory* pFactory = new CharacterFactory;  
  122.   
  123.     //内蕴状态 存储在享元对象内部并且不会随环境改变而改变  
  124.     Character* ch1 = pFactory->GetCharacter('A');  
  125.     ch1->Display();  
  126.   
  127.     //外蕴状态 客户端保存  
  128.     Character* ch2 = pFactory->GetCharacter('B');  
  129.     ch2->SetSize(500, 800);  
  130.     ch2->Display();  
  131.     return 0;  
  132. }  
仔细看了下上面的示例代码,好像写的不怎么好(但也有一定的启发,故此还是保留了),1.GetCharacter函数肯定是没有设计好,没有新生对象的代码,2.Character对象对外部状态进行了保存,设计了两个接口,SetSize--设置外部状态,Display---实现具体业务逻辑  这种写法是否符合享元模式的标准目前来讲是有待商榷的

要点

1、面向对象很好的解决了抽象性的问题,但是作为一个运行在机器中的程序实体,我们需要考虑对象的代价问题。Flyweight设计模式主要解决面向对象的代价问题,一般不触及面向对象的抽象性问题。

2Flyweight采用对象共享的做法来降低系统中对象的个数,从而降低细粒度对象给系统带来的内存压力。在具体实现方面,要注意对象状态的处理。

3、享元模式的优点在于它大幅度地降低内存中对象的数量。但是,它做到这一点所付出的代价也是很高的:享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。另外它将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。

适用性

当以下所有的条件都满足时,可以考虑使用享元模式:

1、一个系统有大量的对象。 

2、这些对象耗费大量的内存。 

3、这些对象的状态中的大部分都可以外部化。 

4、这些对象可以按照内蕴状态分成很多的组,当把外蕴对象从对象中剔除时,每一个组都可以仅用一个对象代替。 

5、软件系统不依赖于这些对象的身份,换言之,这些对象可以是不可分辨的。

满足以上的这些条件的系统可以使用享元对象。最后,使用享元模式需要维护一个记录了系统已有的所有享元的表,而这需要耗费资源。因此,应当在有足够多的享元实例可供共享时才值得使用享元模式。

优缺点

享元模式的优点在于它大幅度地降低内存中对象的数量。但是,它做到这一点所付出的代价也是很高的:

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

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


LCL_data原创于CSDN.NET【http://blog.csdn.net/lcl_data/article/details/8974679】

===================================================================================

Flyweight享元模式

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

内部状态intrinsic和外部状态extrinsic

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

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

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

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

以文字处理软件为例:

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

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

UML图:


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

ConcreteFlyweight:实现Flyweight接口, 并为内部状态( 如果有的话) 增加存储空间。ConcreteFlyweight对象必须是可共享的。它所存储的状态必须是内部的(intrinsic);即,它必须独立于ConcreteFlyweight对象的场景。

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

FlyweightFactory

1) 创建并管理Flyweight对象。

2)确保合理地共享Flyweight。当用户请求一个Flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(如果不存在的话)

Client
1)维持一个对Flyweight的引用。

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

分析
   享元模式可以避免大量非常相似类的开销。在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例数据除了几个参数外基本都是相同的。有时就能够大幅度地减少实例化的类的数量。如果能把那些参数移到类实例的外面,在方法调用时将它们传递进来,就可以通过共享大幅度地减少单个实例的数目。

  比如在文档编辑器的设计过程中,我们如果为没有字母创建一个对象的话,系统可能会因为大量的对象而造成存储开销的浪费。例如一个字母“a”在文档中出现了100000次,而实际上我们可以让这一万个字母“a”共享一个对象,当然因为在不同的位置可能字母“a”有不同的显示效果(例如字体和大小等设置不同),在这种情况我们可以为将对象的状态分为“外部状态”和“内部状态”,将可以被共享(不会变化)的状态作为内部状态存储在对象中,而外部对象(例如上面提到的字体、大小等)我们可以在适当的时候将外部对象最为参数传递给对象(例如在显示的时候,将字体、大小等信息传递给对象)。

  Flyweight的内部状态是用来共享的,Flyweightfactory负责维护一个Flyweight池(存放内部状态的对象),当客户端请求一个共享Flyweight时,这个factory首先搜索池中是否已经有可适用的,如果有,factory只是简单返回送出这个对象,否则,创建一个新的对象,加入到池中,再返回送出这个对象.池为重复或可共享的对象、属性设置一个缓冲,称为内部状态。这些内部状态一般情况下都是不可修改的,也就是在第一个对象、属性被创建后,就不会去修改了(否则就没意义了)。

  Flyweight 对对象的内部状态进行共享,只为每种内部状态创建一个实例,对内部状态使用了单例模式。

  用户不应直接对ConcreteFlyweight类进行实例化,而只能从FlyweightFactory对象得到ConcreteFlyweight对象,这可以保证对它们适当地进行共享。

  存储节约由以下几个因素决定:
  1) 因为共享,实例总数减少的数目
  2) 对象内部状态的平均数目
  3) 外部状态是计算的还是存储的

实现要点
1)享元工厂维护一张享元实例表。

2)享元不可共享的状态需要在外部维护。即删除外部状态:该模式的可用性在很大程度上取决于是否容易识别外部状态并将它从共享对象中删除。

3)按照需求可以对享元角色进行抽象。

4)管理共享对象:引用计数和垃圾回收……

何时采用
1)如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用;

2)还有就是对象的大多数状态可变为外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以考虑所使用享元模式。

3)系统中有大量耗费了大量内存的细粒度对象,并且对外界来说这些对没有任何差别的(或者说经过改造后可以是没有差别的)。

  在文档编辑器例子中如果一个字符对应一个对象,那么一篇文档所要容纳的对象将是非常的庞大耗费大量的内存。而实际组成文档的字符是有限的,是由这些字符不同的组合和排列得到的。所以需要共享,将基本的字符进行共享,将使得字符对象变得有限。

代码如下:

Flyweight.h

复制代码
 1 #ifndef _FLYWEIGHT_H_ 2 #define _FLYWEIGHT_H_ 3  4 #include <string> 5 #include <vector> 6  7 using namespace std; 8  9 //基类,定义操作接口Operation10 class Flyweight11 {12 public:13     //操作外部状态extrinsicState14     virtual void Operation(const string& extrinsicState)=0;15     string GetIntrinsicState();16     virtual ~Flyweight();17 protected:18     Flyweight(string intrinsicState);19 private:20     //内部状态,也可以放在ConcreteFlyweight中21     string _intrinsicState;22 };23 24 class ConcreteFlyweight:public Flyweight25 {26 public:27     //实现接口函数28     virtual void Operation(const string& extrinsicState);29     ConcreteFlyweight(string intrinsicState);30     ~ConcreteFlyweight();31 };32 33 class UnsharedConcreteFlyweight:public Flyweight34 {35 public:36     virtual void Operation(const string& extrinsicState);37     UnsharedConcreteFlyweight(string intrinsicState);38     ~UnsharedConcreteFlyweight();39 };40 41 class FlyweightFactory42 {43 public:44     FlyweightFactory();45     ~FlyweightFactory();46     //获得一个请求的Flyweight对象47     Flyweight* GetFlyweight(string key);48     //获取容器中存储的对象数量49     void GetFlyweightCount();50 protected:51 private:52     //保存内部状态对象的容器53     vector<Flyweight*> m_vecFly;54 };55 #endif
复制代码

Flyweight.cpp

复制代码
 1 #include "Flyweight.h" 2 #include <iostream> 3  4 using namespace std; 5  6 Flyweight::Flyweight(string intrinsicState) 7 { 8     this->_intrinsicState = intrinsicState; 9 }10 11 Flyweight::~Flyweight()12 {}13 14 string Flyweight::GetIntrinsicState()15 {16     return this->_intrinsicState;17 }18 19 ConcreteFlyweight::ConcreteFlyweight(string intrinsicState):Flyweight(intrinsicState)20 {21 }22 23 ConcreteFlyweight::~ConcreteFlyweight()24 {25 }26 27 void ConcreteFlyweight::Operation(const string& extrinsicState)28 {29     cout << this->GetIntrinsicState() << endl;30     cout << extrinsicState << endl;31 }32 33 UnsharedConcreteFlyweight::UnsharedConcreteFlyweight(string intrinsicState):Flyweight(intrinsicState)34 {35 }36 37 UnsharedConcreteFlyweight::~UnsharedConcreteFlyweight()38 {39 }40 41 void UnsharedConcreteFlyweight::Operation(const string& extrinsicState)42 {43     cout << "extrinsicState" << endl;44 }45 46 FlyweightFactory::FlyweightFactory()47 {}48 49 FlyweightFactory::~FlyweightFactory()50 {}51 52 //若该对象已存在,直接返回,否则新建一个对象,存入容器中,再返回53 Flyweight* FlyweightFactory::GetFlyweight(string key)54 {55     vector<Flyweight*>::iterator iter = this->m_vecFly.begin();56     for(;iter!= this->m_vecFly.end();iter++)57     {58         if((*iter)->GetIntrinsicState() == key)59         {60             return *iter;61         }62     }63     Flyweight* fly = new ConcreteFlyweight(key);64     this->m_vecFly.push_back(fly);65     return fly;66 }67 68 void FlyweightFactory::GetFlyweightCount()69 {70     cout << this->m_vecFly.size() << endl;71 }
复制代码

main.cpp

复制代码
 1 #include "Flyweight.h" 2 #include <iostream> 3 #include <string> 4  5 using namespace std; 6  7 int main() 8 { 9     //外部状态extrinsicState10     string extrinsicState = "ext";11 12     //工厂对象,工厂对象13     FlyweightFactory* fc = new FlyweightFactory();14 15     //向工厂申请一个Flyweight对象,且该对象的内部状态值为“hello”16     Flyweight* fly = fc->GetFlyweight("hello");17 18     Flyweight* fly1 = fc->GetFlyweight("hello");19 20     //应用外部状态21     fly->Operation(extrinsicState);22 23     fc->GetFlyweightCount();24 25     return 0;26 }
复制代码

另外一个文本处理的例子

参考:

http://www.dofactory.com/Patterns/PatternFlyweight.aspx

http://sourcemaking.com/design_patterns/flyweight/c%2523#

代码如下:

Document.h

复制代码
 1 #ifndef _DOCUMENT_H_ 2 #define _DOCUMENT_H_ 3  4 #include <string> 5 #include <vector> 6  7 using namespace std; 8  9 // The 'Flyweight' abstract class10 class character11 {12 public:13     //析构函数14     virtual ~character();15     //应用外部状态16     virtual void Display(int width,int height,int ascent,int descent,int pointSize)=0;17     //获取内部状态18     virtual char GetSymbol()=0;19 protected:20     /*-----内部状态---------*/21     char symbol;22     /*----------------------/23 24     /*-----外部状态---------*/25     int width;26     int height;27     int ascent;28     int descent;29     int pointSize;30     /*----------------------*/31     //构造函数32     character(char c);33 };34 35 //A 'ConcreteFlyweight' class36 class characterA:public character37 {38 public:39     characterA(char c);40     ~characterA();41     virtual void Display(int width,int height,int ascent,int descent,int pointSize);42     virtual char GetSymbol();43 protected:44 private:45 };46 47 //B 'ConcreteFlyweight' class48 class characterB:public character49 {50 public:51     characterB(char c);52     ~characterB();53     virtual void Display(int width,int height,int ascent,int descent,int pointSize);54 protected:55 private:56 };57 58 //C 'ConcreteFlyweight' class59 class characterC:public character60 {61 public:62     characterC(char c);63     ~characterC();64     virtual void Display(int width,int height,int ascent,int descent,int pointSize);65 protected:66 private:67 };68 69 //D 'ConcreteFlyweight' class70 class characterD:public character71 {72 public:73     characterD(char c);74     ~characterD();75     virtual void Display(int width,int height,int ascent,int descent,int pointSize);76 protected:77 private:78 };79 /*80 ...81 */82 83 //The 'FlyweightFactory' class84 class characterFactory85 {86 public:87     characterFactory();88     ~characterFactory();89     //申请一个character对象90     character* GetCharacter(char);91     //获取存储的character*数量92     vector<character*>::size_type GetCount();93 private:94     //保存character*的容器,可以换成List等其它容器95     vector<character*> m_vecCharacter;96 };97 98 #endif
复制代码

Document.cpp

复制代码
 1 #include "Document.h" 2 #include <iostream> 3  4 character::character(char c) 5 { 6     this->symbol = c; 7 } 8  9 character::~character()10 {11 }12 13 characterA::characterA(char c):character(c)14 {15 }16 17 characterA::~characterA()18 {19 }20 21 char characterA::GetSymbol()22 {23     return this->symbol;24 }25 26 void characterA::Display(int width,int height,int ascent,int descent,int pointSize)27 {28     //接收并作用外部状态29     this->ascent = ascent;30     this->descent = descent;31     this->height = height;32     this->pointSize = pointSize;33     this->width = width;34 35     cout << this->symbol <<" "36      << this->ascent <<" "37      << this->descent <<" "38      << this->height <<" "39      << this->pointSize <<" "40      << this->width << endl;41 }42 43 characterFactory::characterFactory()44 {}45 46 characterFactory::~characterFactory()47 {}48 49 character* characterFactory::GetCharacter(char c)50 {51     vector<character*>::iterator iter = this->m_vecCharacter.begin();52     for(;iter != this->m_vecCharacter.end();iter++)53     {54         if((*iter)->GetSymbol() == c)55         {56             return *iter;57         }58     }59     character* pf = new characterA(c);60     this->m_vecCharacter.push_back(pf);61     return pf;62 }63 64 vector<character*>::size_type characterFactory::GetCount()65 {66     return this->m_vecCharacter.size();67 }
复制代码

main.cpp

复制代码
 1 #include "Flyweight.h" 2 #include "Document.h" 3 #include <iostream> 4 #include <string> 5  6 using namespace std; 7  8 int main() 9 {10     //存储外部状态11     int ascent = 70;12     int descent = 0;13     int height = 100;14     int width = 120;15     int pointSize = 10;16 17     string test = "AABCDDEFGHI";18     string::iterator it = test.begin();19     characterFactory* pcF = new characterFactory();20     for(;it!=test.end();it++)21     {22         pointSize++;23         char c = *it;24         //申请一个character对象25         character* charc= pcF->GetCharacter(c);26         //应用外部状态27         charc->Display(width,height,ascent,descent,pointSize);28     }29     vector<character*>::size_type sizeChar = pcF->GetCount();30     cout << "count:" << sizeChar << endl;31 32     return 0;33 }
复制代码

 ===================================================================================

无聊的时候,也去QQ游戏大厅玩五子棋或者象棋;作为程序员,看到一个产品,总要去想想它是怎么设计的,怎么完成的,我想这个是所有程序员都会做的事情吧(强迫症???)。有的时候,想完了,还要做一个DEMO出来,才能体现自己的NB,然后还有点小成就感。

在玩五子棋或象棋的时候,我就想过,腾讯那帮伙计是怎么做的呢?五子棋的棋子有黑白两色,难道每次放一个棋子就new一个对象么?象棋有车、马、相、士、帅、炮和兵,是不是每盘棋都要把所有的棋子都new出来呢?如果真的是每一个棋子都new一个,那么再加上那么多人玩;那要new多少对象啊,如果是这样做的话,我想有多少服务器都是搞不定的,可能QQ游戏大厅会比12306还糟糕。那腾讯那帮伙计是如何实现的呢?那就要说到今天总结的享元模式了。

什么是享元模式?

在GOF的《设计模式:可复用面向对象软件的基础》一书中对享元模式是这样说的:运用共享技术有效地支持大量细粒度的对象。

就如上面说的棋子,如果每个棋子都new一个对象,就会存在大量细粒度的棋子对象,这对服务器的内存空间是一种考验,也是一种浪费。我们都知道,比如我在2013号房间和别人下五子棋,2014号房间也有人在下五子棋,并不会因为我在2013号房间,而别人在2014号房间,而导致我们的棋子是不一样的。这就是说,2013号房间和2014号房间的棋子都是一样的,所有的五子棋房间的棋子都是一样的。唯一的不同是每个棋子在不同的房间的不同棋盘的不同位置上。所以,对于棋子来说,我们不用放一个棋子就new一个棋子对象,只需要在需要的时候,去请求获得对应的棋子对象,如果没有,就new一个棋子对象;如果有了,就直接返回棋子对象。这里以五子棋为例子,进行分析,当玩家在棋盘上放入第一个白色棋子时,此时由于没有白色棋子,所以就new一个白色棋子;当另一个玩家放入第一个黑色棋子时,此时由于没有黑色棋子,所以就需要new一个黑色棋子;当玩家再次放入一个白色棋子时,就去查询是否有已经存在的白色棋子对象,由于第一次已经new了一个白色棋子对象,所以,现在不会再次new一个白色棋子对象,而是返回以前new的白色棋子对象;对于黑色棋子,亦是同理;获得了棋子对象,我们只需要设置棋子的不同棋盘位置即可。

UML类图

果冻想 | 一个原创文章分享网站

Flyweight:描述一个接口,通过这个接口flyweight可以接受并作用于外部状态;

ConcreteFlyweight:实现Flyweight接口,并为定义了一些内部状态,ConcreteFlyweight对象必须是可共享的;同时,它所存储的状态必须是内部的;即,它必须独立于ConcreteFlyweight对象的场景;

UnsharedConcreteFlyweight:并非所有的Flyweight子类都需要被共享。Flyweight接口使共享成为可能,但它并不强制共享。

FlyweightFactory:创建并管理flyweight对象。它需要确保合理地共享flyweight;当用户请求一个flyweight时,FlyweightFactory对象提供一个已创建的实例,如果请求的实例不存在的情况下,就新创建一个实例;

Client:维持一个对flyweight的引用;同时,它需要计算或存储flyweight的外部状态。

实现要点

根据我们的经验,当要将一个对象进行共享时,就需要考虑到对象的状态问题了;不同的客户端获得共享的对象之后,可能会修改共享对象的某些状态;大家都修改了共享对象的状态,那么就会出现对象状态的紊乱。对于享元模式,在实现时一定要考虑到共享对象的状态问题。那么享元模式是如何实现的呢?

在享元模式中,有两个非常重要的概念:内部状态和外部状态。

内部状态存储于flyweight中,它包含了独立于flyweight场景的信息,这些信息使得flyweight可以被共享。而外部状态取决于flyweight场景,并根据场景而变化,因此不可共享。用户对象负责在必要的时候将外部状态传递给flyweight。

flyweight执行时所需的状态必定是内部的或外部的。内部状态存储于ConcreteFlyweight对象之中;而外部对象则由Client对象存储或计算。当用户调用flyweight对象的操作时,将该状态传递给它。同时,用户不应该直接对ConcreteFlyweight类进行实例化,而只能从FlyweightFactory对象得到ConcreteFlyweight对象,这可以保证对它们适当地进行共享;由于共享一个实例,所以在创建这个实例时,就可以考虑使用单例模式来进行实现。

享元模式的工厂类维护了一个实例列表,这个列表中保存了所有的共享实例;当用户从享元模式的工厂类请求共享对象时,首先查询这个实例表,如果不存在对应实例,则创建一个;如果存在,则直接返回对应的实例。

代码实现

#include <iostream>#include <map>#include <vector>using namespace std;typedef struct pointTag{    int x;    int y;    pointTag(){}    pointTag(int a, int b)    {        x = a;y = b;    }    bool operator <(const pointTag& other) const    {        if (x < other.x)        {            return true;        }        else if (x == other.x)        {            return y < other.y;        }        return false;    }}POINT;typedef enum PieceColorTag{    BLACK,    WHITE}PIECECOLOR;class CPiece{public:    CPiece(PIECECOLOR color) : m_color(color){}    PIECECOLOR GetColor() { return m_color; }    // Set the external state    void SetPoint(POINT point) { m_point = point; }    POINT GetPoint() { return m_point; }protected:    // Internal state    PIECECOLOR m_color;    // external state    POINT m_point;};class CGomoku : public CPiece{public:    CGomoku(PIECECOLOR color) : CPiece(color){}};class CPieceFactory{public:    CPiece *GetPiece(PIECECOLOR color)    {        CPiece *pPiece = NULL;if (m_vecPiece.empty()){    pPiece = new CGomoku(color);    m_vecPiece.push_back(pPiece);}else{    for (vector<CPiece *>::iterator it = m_vecPiece.begin(); it != m_vecPiece.end(); ++it)    {        if ((*it)->GetColor() == color){    pPiece = *it;    break;}    }    if (pPiece == NULL)    {pPiece = new CGomoku(color);m_vecPiece.push_back(pPiece);    } }    return pPiece;    }        ~CPieceFactory()    {        for (vector<CPiece *>::iterator it = m_vecPiece.begin(); it != m_vecPiece.end(); ++it)        {            if (*it != NULL)    {delete *it;*it = NULL;    }}    }private:    vector<CPiece *> m_vecPiece;};class CChessboard{public:    void Draw(CPiece *piece)    {if (piece->GetColor()){            cout<<"Draw a White"<<" at ("<<piece->GetPoint().x<<","<<piece->GetPoint().y<<")"<<endl;}else{    cout<<"Draw a Black"<<" at ("<<piece->GetPoint().x<<","<<piece->GetPoint().y<<")"<<endl;}m_mapPieces.insert(pair<POINT, CPiece *>(piece->GetPoint(), piece));    }    void ShowAllPieces()    {    for (map<POINT, CPiece *>::iterator it = m_mapPieces.begin(); it != m_mapPieces.end(); ++it){            if (it->second->GetColor())    {cout<<"("<<it->first.x<<","<<it->first.y<<") has a White chese."<<endl;    }    else    {cout<<"("<<it->first.x<<","<<it->first.y<<") has a Black chese."<<endl;    }}    }private:    map<POINT, CPiece *> m_mapPieces;};int main(){    CPieceFactory *pPieceFactory = new CPieceFactory();    CChessboard *pCheseboard = new CChessboard();    // The player1 get a white piece from the pieces bowl    CPiece *pPiece = pPieceFactory->GetPiece(WHITE);    pPiece->SetPoint(POINT(2, 3));    pCheseboard->Draw(pPiece);    // The player2 get a black piece from the pieces bowl    pPiece = pPieceFactory->GetPiece(BLACK);    pPiece->SetPoint(POINT(4, 5));    pCheseboard->Draw(pPiece);    // The player1 get a white piece from the pieces bowl    pPiece = pPieceFactory->GetPiece(WHITE);    pPiece->SetPoint(POINT(2, 4));    pCheseboard->Draw(pPiece);    // The player2 get a black piece from the pieces bowl    pPiece = pPieceFactory->GetPiece(BLACK);    pPiece->SetPoint(POINT(3, 5));    pCheseboard->Draw(pPiece);    /*......*/    //Show all cheses    cout<<"Show all cheses"<<endl;    pCheseboard->ShowAllPieces();    if (pCheseboard != NULL)    { delete pCheseboard;pCheseboard = NULL;    }    if (pPieceFactory != NULL)    {delete pPieceFactory;pPieceFactory = NULL;    }}

内部状态包括棋子的颜色,外部状态包括棋子在棋盘上的位置。最终,我们省去了多个实例对象存储棋子颜色的空间,从而达到了空间的节约。

在上面的代码中,我建立了一个CCheseboard用于表示棋盘,棋盘类中保存了放置的黑色棋子和白色棋子;这就相当于在外部保存了共享对象的外部状态;对于棋盘对象,我们是不是又可以使用享元模式呢?再设计一个棋局类进行管理棋盘上的棋子布局,用来保存外部状态。对于这个,这里不进行讨论了。

优点

享元模式可以避免大量非常相似对象的开销。在程序设计时,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例数据除了几个参数外基本都是相同的,使用享元模式就可以大幅度地减少对象的数量。

使用场合

Flyweight模式的有效性很大程度上取决于如何使用它以及在何处使用它。当以下条件满足时,我们就可以使用享元模式了。

  1. 一个应用程序使用了大量的对象;
  2. 完全由于使用大量的对象,造成很大的存储开销;
  3. 对象的大多数状态都可变为外部状态;
  4. 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。

扩展

之前总结了组合模式组合模式,现在回过头来看看,享元模式就好比在组合模式的基础上加上了一个工厂类,进行共享控制。是的,组合模式有的时候会产生很多细粒度的对象,很多时候,我们会将享元模式和组合模式进行结合使用。

总结

使用享元模式可以避免大量相似对象的开销,减小了空间消耗;而空间的消耗是由以下几个因素决定的:

  1. 实例对象减少的数目;
  2. 对象内部状态的数目;对象内部状态越多,消耗的空间也会越少;
  3. 外部状态是计算的还是存储的;由于外部状态可能需要存储,如果外部状态存储起来,那么空间的节省就不会太多。

共享的Flyweight越多,存储节约也就越多,节约量随着共享状态的增多而增大。当对象使用大量的内部及外部状态,并且外部状态是计算出来的而非存储的时候,节约量将达到最大。所以,可以使用两种方法来节约存储:用共享减少内部状态的消耗;用计算时间换取对外部状态的存储。

同时,在实现的时候,一定要控制好外部状态与共享对象的对应关系,比如我在代码实现部分,在CCheseboard类中使用了一个map进行彼此之间的映射,这个映射在实际开发中需要考虑的。

好了,享元模式就总结到这里了。希望大家和我分享你对设计模式的理解。我坚信:分享使我们更进步。
PS:至于腾讯那帮伙计到底是如何实现QQ游戏大厅的,我也不知道,这里也完全是猜测的,请不要以此为基准。

2014年1月7日 于大连,东软。

0 0
原创粉丝点击