第二十六章 项目多也别傻做--享元模式
26.1 项目多也别傻做
比如现在的大型的博客网站,电子商务网站,里面每一个博客或商家也可以理解为一个小的网站。利用用户ID号的不同来区分不同的用户,具体数据和模板可以不同,但代码核心和数据库确是共享的。
26.2 享元模式
运用共享技术有效地支持大量细粒度的对象。
#pragma once #include <iostream> #include <hash_map> class FlyWeight { public: virtual void Operation(int extrinsicstate) = 0; virtual ~FlyWeight(){}; }; class ConcreteFlyWeight : public FlyWeight { public: void Operation(int extrinsicstate) { std::cout << "具体的 FlyWeight:" << extrinsicstate << std::endl; }; }; class UnSharedFlyWeight : public FlyWeight { public: void Operation(int extrinsicstate) { std::cout << "不共享的具体的 FlyWeight:" << extrinsicstate << std::endl; }; }; class FlyWeightFactory { public: FlyWeightFactory() { m_HashTable['X'] = new ConcreteFlyWeight(); m_HashTable['Y'] = new ConcreteFlyWeight(); m_HashTable['Z'] = new ConcreteFlyWeight(); }; ~FlyWeightFactory() { delete m_HashTable['X']; delete m_HashTable['Y']; delete m_HashTable['Z']; } FlyWeight* GetFlyWeight(char key) { return (FlyWeight*)m_HashTable[key]; }; private: stdext::hash_map<char ,FlyWeight*> m_HashTable; };
客户端
#include "stdafx.h" #include "FlyWeight.h" int _tmain(int argc, _TCHAR* argv[]) { int extrinsicstate = 22; FlyWeightFactory* f = new FlyWeightFactory(); FlyWeight* fx = f->GetFlyWeight('X'); fx->Operation(--extrinsicstate); FlyWeight* fy = f->GetFlyWeight('Y'); fy->Operation(--extrinsicstate); FlyWeight* fz = f->GetFlyWeight('Z'); fz->Operation(--extrinsicstate); UnSharedFlyWeight* uf = new UnSharedFlyWeight(); uf->Operation(--extrinsicstate); delete f; return 0; }
Flyweight类,它是所有具体享元类的超类或接口,通过这个接口,Flyweight可以接受并作用于外部状态。
ConcreteFlyweight是继承Flyweight超类或实现Flyweight接口,并为内部状态增加存储空间。
UnsharedConcreteFlyweight是指那些不需要共享的FlyWeight子类,因为FlyWeight接口共享成为可能,但他不强制共享。有它的存在解决了那些不需要共享对象的问题。
FlyweightFactory是一个享元工厂,用来创建并管理Flyweight对象。它主要是用来确保合理地共享Flyweight,当用户请求一个Flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个新的实例。
26.3 网站共享代码
#pragma once #include <string> #include <iostream> #include <hash_map> //网站抽象类 class WebSite { public: virtual void Use(void) = 0; virtual ~WebSite(){}; }; //具体网站类 class ConcreteWebSite : public WebSite { public: ConcreteWebSite(std::string name) { m_name = name; }; void Use(void) { std::cout << "网站分类:" << m_name << std::endl; }; private: std::string m_name; }; //网站工厂 class WebSiteFactory { public: WebSite* GetWebSiteFactory(std::string key) { m_hashIter = m_HashTable.find(key); if ( m_hashIter == m_HashTable.end()) {//若不存在这个对象,则实例化它以后再返回 m_HashTable[key] = new ConcreteWebSite(key); } return (WebSite*)m_HashTable[key]; }; //获得网站分类的总数 int GetWebSiteCount() { return m_HashTable.size(); } ~WebSiteFactory() { if (GetWebSiteCount() > 0) { m_hashIter = m_HashTable.begin(); while (m_hashIter != m_HashTable.end()) { //ConcreteWebSite* p = static_cast<ConcreteWebSite*>(m_hashIter->second); WebSite* p = m_hashIter->second; delete p; m_hashIter++; } } m_HashTable.clear(); } private: stdext::hash_map<std::string ,WebSite*> m_HashTable; stdext::hash_map<std::string ,WebSite*>::iterator m_hashIter; };
客户端:
#include "stdafx.h" #include "WebSite.h" int _tmain(int argc, _TCHAR* argv[]) { WebSiteFactory* f = new WebSiteFactory(); //实例化产品展示的网站的对象 WebSite* fx = f->GetWebSiteFactory("产品展示"); fx->Use(); //共享上方生成的对象,不再实例化 WebSite* fy = f->GetWebSiteFactory("产品展示"); fy->Use(); WebSite* fz = f->GetWebSiteFactory("产品展示"); fz->Use(); WebSite* fa = f->GetWebSiteFactory("BLOG"); fa->Use(); WebSite* fb = f->GetWebSiteFactory("BLOG"); fb->Use(); WebSite* fc = f->GetWebSiteFactory("BLOG"); fc->Use(); //统计实例化个数,结果应该为2 std::cout << "总数: " << f->GetWebSiteCount() << std::endl; delete f; return 0; }
这样写算是基本实现了享元模式的共享对象的目的,也就是说,不管建几个网站,只要是"产品展示",都是一样的,只要是BLOG,也是完全相同的。所以上面的代码只体现了它们共享的部分而没有体现对象之间的不同成分。
26.4 内部状态与外部状态
享元模式的内部状态:不会随环境改变而改变的共享部分。享元模式的内部状态存储于ConcreteFlyweight中。
享元模式的外部状态:随着环境的改变而改变的,不可以共享的状态就是外部状态了。
享元模式可以避免大量的非常相似类的开销。在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例除了几个参数外基本上都是相同的,有时就能够受大幅度地减少需要实例化的类的数量。如果能把那些参数移到类实例的外面,在方法调用时将它们传递进来,就可以通过共享大幅度地减少单个实例的数目。
在上面的例子中,客户的帐号就是外部状态,应该由专门的对象来处理。
//用户 class User { public: User(std::string name) { m_name = name; }; std::string GetUser(void) { return m_name; }; private: std::string m_name; }; //网站类,网站工厂类等等。其他的代码同26.3
客户端:
#include "stdafx.h" #include "WebSite.h" int _tmain(int argc, _TCHAR* argv[]) { WebSiteFactory* f = new WebSiteFactory(); WebSite* fx = f->GetWebSiteFactory("产品展示"); fx->Use(new User("小菜")); WebSite* fy = f->GetWebSiteFactory("产品展示"); fy->Use(new User("大鸟")); WebSite* fz = f->GetWebSiteFactory("产品展示"); fz->Use(new User("jiao jiao")); WebSite* fa = f->GetWebSiteFactory("BLOG"); fa->Use(new User("lao wan tong")); WebSite* fb = f->GetWebSiteFactory("BLOG"); fb->Use(new User("tao gu liu xian")); WebSite* fc = f->GetWebSiteFactory("BLOG"); fc->Use(new User("nan hai e shen")); std::cout << "总数: " << f->GetWebSiteCount() << std::endl; delete f; return 0; }
由于用了享元模式,哪怕你接手了1000个网站的需求,只要要求相同或类似,你的实际开发代码也就是分类的那几种。
26.5 享元模式应用
如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用,还有就是对象的大多数状态可以外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以考虑使用享元模式。有了共享对象,实例总数就大大减少了,节约量随着共享状态的增多而增大。
虽说享元模式更多的时候是一种底层的设计模式,但现实世界中也是有应用的。比如说休闲游戏开发中,像围棋,五子棋,跳棋等,棋子的颜色是不会变化的,所以这个时候棋子就是内部状态,而棋子的方位坐标应该就是棋子的外部状态。
使用享元模式需要维护一个记录了系统已有的所有享元的列表,而本身需要耗费资源,另外享元模式也会使得系统变得更加复杂。因此应当在有足够多的对象实例可供共享的时候才值得使用享元模式。