在软件开发过程,若是咱们须要重复使用某个对象的时候,若是咱们重复地使用new建立这个对象的话,这样咱们在内存就须要屡次地去申请内存空间了,这样可能会出现内存使用愈来愈多的状况,这样的问题是很是严重,然而享元模式能够解决这个问题,下面具体看看享元模式是如何去解决这个问题的。html
在前面说了,享元模式能够解决上面的问题了,在介绍享元模式以前,让咱们先要分析下若是去解决上面那个问题,上面的问题就是重复建立了同一个对象,若是让咱们去解决这个问题确定会这样想:“既然都是同一个对象,能不能只建立一个对象,而后下次须要建立这个对象的时候,让它直接用已经建立好了的对象就行了”,也就是说——让一个对象共享。不错,这个也是享元模式的实现精髓所在。c#
介绍完享元模式的精髓以后,让咱们具体看看享元模式的正式定义:编辑器
享元模式——运用共享技术有效地支持大量细粒度的对象。享元模式能够避免大量类似类的开销,在软件开发中若是须要生成大量细粒度的类实例来表示数据,若是这些实例除了几个参数外基本上都是相同的,这时候就可使用享元模式来大幅度减小须要实例化类的数量。若是能把这些参数(指的这些类实例不一样的参数)移动类实例外面,在方法调用时将他们传递进来,这样就能够经过共享大幅度地减小单个实例的数目。(这个也是享元模式的实现要领),然而咱们把类实例外面的参数称为享元对象的外部状态,把在享元对象内部定义称为内部状态。具体享元对象的内部状态与外部状态的定义为:ide
内部状态:在享元对象的内部而且不会随着环境的改变而改变的共享部分函数
外部状态:随环境改变而改变的,不能够共享的状态。性能
分析完享元模式的实现思路以后,相信你们实现享元模式确定没什么问题了,下面以一个世纪的应用来实现下享元模式。这个例子是:一个文本编辑器中会出现不少字面,使用享元模式去实现这个文本编辑器的话,会把每一个字面作成一个享元对象。享元对象的内部状态就是这个字面,而字母在文本中的位置和字体风格等其余信息就是它的外部状态。下面就以这个例子来实现下享元模式,具体实现代码以下:字体
/// <summary> /// 客户端调用 /// </summary> class Client { static void Main(string[] args) { // 定义外部状态,例如字母的位置等信息 int externalstate = 10; // 初始化享元工厂 FlyweightFactory factory = new FlyweightFactory(); // 判断是否已经建立了字母A,若是已经建立就直接使用建立的对象A Flyweight fa = factory.GetFlyweight("A"); if (fa != null) { // 把外部状态做为享元对象的方法调用参数 fa.Operation(--externalstate); } // 判断是否已经建立了字母B Flyweight fb = factory.GetFlyweight("B"); if (fb != null) { fb.Operation(--externalstate); } // 判断是否已经建立了字母C Flyweight fc = factory.GetFlyweight("C"); if (fc != null) { fc.Operation(--externalstate); } // 判断是否已经建立了字母D Flyweight fd= factory.GetFlyweight("D"); if (fd != null) { fd.Operation(--externalstate); } else { Console.WriteLine("驻留池中不存在字符串D"); // 这时候就须要建立一个对象并放入驻留池中 ConcreteFlyweight d = new ConcreteFlyweight("D"); factory.flyweights.Add("D", d); } Console.Read(); } } /// <summary> /// 享元工厂,负责建立和管理享元对象 /// </summary> public class FlyweightFactory { // 最好使用泛型Dictionary<string,Flyweighy> //public Dictionary<string, Flyweight> flyweights = new Dictionary<string, Flyweight>(); public Hashtable flyweights = new Hashtable(); public FlyweightFactory() { flyweights.Add("A", new ConcreteFlyweight("A")); flyweights.Add("B", new ConcreteFlyweight("B")); flyweights.Add("C", new ConcreteFlyweight("C")); } public Flyweight GetFlyweight(string key) { // 更好的实现以下 //Flyweight flyweight = flyweights[key] as Flyweight; //if (flyweight == null) //{ // Console.WriteLine("驻留池中不存在字符串" + key); // flyweight = new ConcreteFlyweight(key); //} //return flyweight; return flyweights[key] as Flyweight; } } /// <summary> /// 抽象享元类,提供具体享元类具备的方法 /// </summary> public abstract class Flyweight { public abstract void Operation(int extrinsicstate); } // 具体的享元对象,这样咱们不把每一个字母设计成一个单独的类了,而是做为把共享的字母做为享元对象的内部状态 public class ConcreteFlyweight : Flyweight { // 内部状态 private string intrinsicstate ; // 构造函数 public ConcreteFlyweight(string innerState) { this.intrinsicstate = innerState; } /// <summary> /// 享元类的实例方法 /// </summary> /// <param name="extrinsicstate">外部状态</param> public override void Operation(int extrinsicstate) { Console.WriteLine("具体实现类: intrinsicstate {0}, extrinsicstate {1}", intrinsicstate, extrinsicstate); } }
在享元模式的实现中,咱们没有像以前同样,把一个细粒度的类实例设计成一个单独的类,而是把它做为共享对象的内部状态放在共享类的内部定义,具体的解释注释中都有了,你们能够参考注释去进一步理解享元模式。this
看完享元模式的实现以后,为了帮助你们理清楚享元模式中各种之间的关系,下面给出上面实现代码中的类图,以下所示:spa
(摘自http://www.cnblogs.com/zhenyulu/articles/55793.html)设计
在上图中,涉及的角色以下几种角色:
抽象享元角色(Flyweight):此角色是全部的具体享元类的基类,为这些类规定出须要实现的公共接口。那些须要外部状态的操做能够经过调用方法以参数形式传入。
具体享元角色(ConcreteFlyweight):实现抽象享元角色所规定的接口。若是有内部状态的话,能够在类内部定义。
享元工厂角色(FlyweightFactory):本角色复杂建立和管理享元角色。本角色必须保证享元对象能够被系统适当地共享,当一个客户端对象调用一个享元对象的时候,享元工厂角色检查系统中是否已经有一个符合要求的享元对象,若是已经存在,享元工厂角色就提供已存在的享元对象,若是系统中没有一个符合的享元对象的话,享元工厂角色就应当建立一个合适的享元对象。
客户端角色(Client):本角色须要存储全部享元对象的外部状态。
注:上面的实现只是单纯的享元模式,同时还有复合的享元模式,因为复合享元模式较复杂,这里就不给出实现了。
分析完享元模式的实现以后,让咱们继续分析下享元模式的优缺点:
优势:
下降了系统中对象的数量,从而下降了系统中细粒度对象给内存带来的压力。
缺点:
为了使对象能够共享,须要将一些状态外部化,这使得程序的逻辑更复杂,使系统复杂化。
享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。
在下面全部条件都知足时,能够考虑使用享元模式:
一个系统中有大量的对象;
这些对象耗费大量的内存;
这些对象中的状态大部分均可以被外部化
这些对象能够按照内部状态分红不少的组,当把外部对象从对象中剔除时,每个组均可以仅用一个对象代替
软件系统不依赖这些对象的身份,
知足上面的条件的系统可使用享元模式。可是使用享元模式须要额外维护一个记录子系统已有的全部享元的表,而这也须要耗费资源,因此,应当在有足够多的享元实例可共享时才值得使用享元模式。
注:在.NET类库中,string类的实现就使用了享元模式,更多内容能够参考字符串驻留池的介绍,同时也能够参考这个博文深刻理解.NET中string类的设计——http://www.cnblogs.com/artech/archive/2010/11/25/internedstring.html
到这里,享元模式的介绍就结束了,享元模式主要用来解决因为大量的细粒度对象所形成的内存开销的问题,它在实际的开发中并不经常使用,能够做为底层的提高性能的一种手段。