12,享元模式(Flyweight Pattern)是以共享的方式高效的支持大量的细粒度的对象。
想想我们编辑文档用的wps,文档里文字很多都是重复的,我们不可能为每一个出现的汉字都创建独立的空间,这样代价太大,最好的办法就是共享其中相同的部分,使得需要创建的对象降到最小,这个就是享元模式的核心,即运用共享技术有效地支持大量细粒度的对象。
享元对象能做到共享的关键是区分内蕴状态(Internal State)和外蕴状态(External State)。
内蕴状态:是存储在享元对象内部并且不会随环境改变而改变。因此内蕴状态并可以共享。
外蕴状态:是随环境改变而改变的、不可以共享的状态。享元对象的外蕴状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。外蕴状态与内蕴状态是相互独立的。
抽象享元类(Flyweight)
它是所有具体享元类的超类。为这些类规定出需要实现的公共接口,那些需要外蕴状态(Exte的操作可以通过方法的参数传入。抽象享元的接口使得享元变得可能,但是并不强制子类实行共享,因此并非所有的享元对象都是可以共享的。
具体享元类(ConcreteFlyweight)
具体享元类实现了抽象享元类所规定的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享。有时候具体享元类又称为单纯具体享元类,因为复合享元类是由单纯具体享元角色通过复合而成的。
不能共享的具体享元类(UnsharableFlyweight)
不能共享的享元类,又叫做复合享元类。一个复合享元对象是由多个单享元对象组成,这些组成的对象是可以共享的,但是复合享元类本身并不能共享。
享元工厂类(FlyweightFactoiy)
享元工厂类负责创建和管理享元对象。当一个客户端对象请求一个享元对象的时候,享元工厂需要检查系统中是否已经有一个符合要求的享元对象,如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有适当的享元对象的话,享元工厂角色就应当创建一个新的合适的享元对象。
客户类(Client)
客户类需要自行存储所有享元对象的外蕴状态。
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(int, int) = 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(int, int); 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(int, int); 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 #include <iostream> 2 using namespace std; 3 #include "string" 4 #include "map" 5 6 class Person 7 { 8 public: 9 Person(string name, int age) 10 { 11 this->m_name = name; 12 this->age = age; 13 } 14 virtual void printT() = 0; 15 16 protected: 17 string m_name; 18 int age; 19 }; 20 21 class Teacher : public Person 22 { 23 public: 24 Teacher(string name, int age, string id) : Person(name, age) 25 { 26 this->m_id = id; 27 } 28 void printT() 29 { 30 cout << "name:" << m_name << " age:" << age << " m_id:" << m_id << endl; 31 } 32 protected: 33 private: 34 string m_id; 35 }; 36 37 38 //完成 老师结点 存储 39 40 class FlyWeightTeacherFactory 41 { 42 public: 43 FlyWeightTeacherFactory() 44 { 45 map1.clear(); 46 } 47 48 ~FlyWeightTeacherFactory() 49 { 50 while ( !map1.empty()) 51 { 52 Person *tmp = NULL; 53 map<string, Person *>::iterator it = map1.begin(); 54 tmp = it->second; 55 map1.erase(it); //把第一个结点 从容器中删除 56 delete tmp; 57 } 58 } 59 60 Person * GetTeacher(string id) 61 { 62 Person *tmp = NULL; 63 map<string, Person *>::iterator it ; 64 it = map1.find(id); 65 if (it == map1.end()) //没有找到 66 { 67 string tmpname; 68 int tmpage; 69 cout << "\n请输入老师name:"; 70 cin >> tmpname; 71 72 cout << "\n请输入老师age:"; 73 cin >> tmpage; 74 75 tmp = new Teacher(tmpname, tmpage, id); 76 map1.insert(pair<string, Person*>(id, tmp) ); 77 } 78 else 79 { 80 tmp = it->second; 81 } 82 return tmp; 83 } 84 private: 85 map<string, Person *> map1; 86 87 }; 88 89 void main() 90 { 91 Person *p1 = NULL; 92 Person *p2 = NULL; 93 FlyWeightTeacherFactory *fwtf = new FlyWeightTeacherFactory; 94 p1 = fwtf->GetTeacher("001"); 95 p1->printT(); 96 97 p2 = fwtf->GetTeacher("001"); 98 p2->printT(); 99 100 delete fwtf; 101 102 cout<<"hello..."<<endl; 103 system("pause"); 104 return ; 105 }
要点
1、面向对象很好的解决了抽象性的问题,但是作为一个运行在机器中的程序实体,我们需要考虑对象的代价问题。Flyweight设计模式主要解决面向对象的代价问题,一般不触及面向对象的抽象性问题。
2、Flyweight采用对象共享的做法来降低系统中对象的个数,从而降低细粒度对象给系统带来的内存压力。在具体实现方面,要注意对象状态的处理。
3、享元模式的优点在于它大幅度地降低内存中对象的数量。但是,它做到这一点所付出的代价也是很高的:享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。另外它将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。
适用性
当以下所有的条件都满足时,可以考虑使用享元模式:
1、一个系统有大量的对象。
2、这些对象耗费大量的内存。
3、这些对象的状态中的大部分都可以外部化。
4、这些对象可以按照内蕴状态分成很多的组,当把外蕴对象从对象中剔除时,每一个组都可以仅用一个对象代替。
5、软件系统不依赖于这些对象的身份,换言之,这些对象可以是不可分辨的。
满足以上的这些条件的系统可以使用享元对象。最后,使用享元模式需要维护一个记录了系统已有的所有享元的表,而这需要耗费资源。因此,应当在有足够多的享元实例可供共享时才值得使用享元模式。
优缺点
享元模式的优点在于它大幅度地降低内存中对象的数量。但是,它做到这一点所付出的代价也是很高的:
1、享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。
2、享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。