好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

步步为营 .NET 设计模式学习笔记 十七、Flyweight(享元模式)

步步为营 .NET 设计模式学习笔记 十七、Flyweight(享元模式)

概述  

面向对象的思想很好地解决了抽象性的问题,一般也不会出现性能上的问题。但是在某些情况下,对象的数量可能会太多,从而导致了运行时的代价。那么我们如何去避免大量细粒度的对象,同时又不影响客户程序使用面向对象的方式进行操作?

意图  

运用共享技术有效地支持大量细粒度的对象。[GOF 《设计模式》]

结构图

1.单纯享元模式的结构

在单纯享元模式中,所有的享元对象都是可以共享的。单纯享元模式所涉及的角色如下:

抽象享元(Flyweight)角色:此角色是所有的具体享元类的超类,为这些类规定出需要实现的公共接口。那些需要外蕴状态(External State)的操作可以通过调用商业方法以参数形式传入。

具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享的。

享元工厂(FlyweightFactory)角色:本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象调用一个享元对象的时候,享元工厂角色会检查系统中是否已经有一个复合要求的享元对象。如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个合适的享元对象。

客户端(Client)角色:本角色需要维护一个对所有享元对象的引用。本角色需要自行存储所有享元对象的外蕴状态。

2.复合享元模式的结构

单纯享元模式中,所有的享元对象都可以直接共享。下面考虑一个较为复杂的情况,即将一些单纯享元使用合成模式加以复合,形成复合享元对象。这样的复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享。

复合享元模式的类图如下图所示:

享元模式所涉及的角色有抽象享元角色、具体享元角色、复合享元角色、享员工厂角色,以及客户端角色等。

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

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

复合享元(UnsharableFlyweight)角色:复合享元角色所代表的对象是不可以共享的,但是一个复合享元对象可以分解成为多个本身是单纯享元对象的组合。复合享元角色又称做不可共享的享元对象。

享元工厂(FlyweightFactoiy)角色:本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象请求一个享元对象的时候,享元工厂角色需要检查系统中是否已经有一个符合要求的享元对象,如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个新的合适的享元对象。

客户端(Client)角色:本角色还需要自行存储所有享元对象的外蕴状态。

生活中的例子  

享元模式使用共享技术有效地支持大量细粒度的对象。公共交换电话网(PSTN)是享元的一个例子。有一些资源例如拨号音发生器、振铃发生器和拨号接收器是必须由所有用户共享的。当一个用户拿起听筒打电话时,他不需要知道使用了多少资源。对于用户而言所有的事情就是有拨号音,拨打号码,拨通电话。

图2  使用拨号音发生器例子的享元模式对象图

.NET 框架中的Flyweight

Flyweight更多时候的时候一种底层的设计模式,在我们的实际应用程序中使用的并不是很多。在.NET中的String类型其实就是运用了Flyweight模式。可以想象,如果每次执行string s1 = “abcd”操作,都创建一个新的字符串对象的话,内存的开销会很大。所以.NET中如果第一次创建了这样的一个字符串对象s1,下次再创建相同的字符串s2时只是把它的引用指向“abcd”,这样就实现了“abcd”在内存中的共享。可以通过下面一个简单的程序来演示s1和s2的引用是否一致:

public   class   Program

  {

      public   static   void   Main( string [] args)

      {

          string   s1 =  "abcd" ;

          string   s2 =  "abcd" ;

          string   s3 =  "ab" ;

          string   s4 = s3 +  "cd" ;

          Console.WriteLine(Object.ReferenceEquals(s1, s2)+ "\n" );

          Console.WriteLine(Object.ReferenceEquals(s1, s4) +  "\n" );

          Console.WriteLine(s1.Equals(s4));

          Console.ReadLine();

      }

  }

可以看到,输出的结果为:

True

False

True

示例用例图

一个User用户实例和UserFactory.

代码设计

先创建User.cs:

01 public   class   User

02 {

03      private   string   _UserName;

04      private   string   _Age;

05  

06      public   string   UserName

07      {

08          get   {  return   _UserName; }

09          set   { _UserName = value; }

10      }

11  

12      public   string   Age

13      {

14          get   {  return   _Age; }

15          set   { _Age = value; }

16      }

17  

18      public   User( string   userName,  string   age)

19      {

20          this .UserName = userName;

21          this .Age = age;

22      }

23 }

再创建UserFactory.cs:

01 public   class   UserFactory

02 {

03      private   Hashtable modelList =  new   Hashtable();

04  

05      private   static   UserFactory _UserInstance;

06  

07      public   static   UserFactory GetUserInstance()

08      {

09          if   (_UserInstance ==  null )

10          {

11              _UserInstance =  new   UserFactory();

12          }

13          return   _UserInstance;

14      }

15  

16  

17      public   User GetUser( string   userName,  string   Age)

18      {

19          User user = modelList[userName]  as   User;

20          if   (user ==  null )

21          {

22              modelList.Add(userName,  new   User(userName, Age));

23              user = modelList[userName]  as   User;

24          }

25          return   user;

26      }

27  

28      public   int   GetUserCount()

29      {

30          return   modelList.Count;

31      }

32 }

最后调用:

01 public   partial   class   Run : Form

02   {

03       public   Run()

04       {

05           InitializeComponent();

06       }

07  

08       private   void   btnRun_Click( object   sender, EventArgs e)

09       {

10  

11           //-------------------------------------

12  

13           rtbResult.AppendText( string .Format( "现在的内存是{0}.\n" , GC.GetTotalMemory( false )));

14           Random random =  new   Random();

15           UserFactory userFactory =  new   UserFactory();

16           for   ( int   i = 0; i < 100000; i++)

17           {

18               userFactory.GetUser(random.Next(3).ToString(), (i % 20).ToString());

19           }

20           rtbResult.AppendText( string .Format( "创建两个实例的个数是{0},消耗的内存是{1}." , userFactory.GetUserCount(), GC.GetTotalMemory( false )));

21  

22       }

23  

24   }

运行结果如下图:

再看如下调用:

01 public   partial   class   Run : Form

02 {

03      public   Run()

04      {

05          InitializeComponent();

06      }

07  

08      private   void   btnRun_Click( object   sender, EventArgs e)

09      {

10          //-------------------------------------

11          rtbResult.AppendText( string .Format( "现在的内存是{0}.\n" , GC.GetTotalMemory( false )));

12          Random random =  new   Random();

13          List<Flyweight.User> userlist =  new   List<Flyweight.User>();

14          for   ( int   i = 0; i < 100000; i++)

15          {

16              Flyweight.User user =  new   Flyweight.User(random.Next(3).ToString(), (i % 20).ToString());

17              userlist.Add(user);

18          }

19          rtbResult.AppendText( string .Format( "创建两个实例的个数是{0},消耗的内存是{1}." , userlist.Count, GC.GetTotalMemory( false )));

20      }

21  

22 }

运行结果如下图:

效果及实现要点  

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

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

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

4.享元工厂维护一张享元实例表。

5.享元不可共享的状态需要在外部维护。

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

注意事项

 

1.享元模式通常针对细粒度的对象,如果这些对象比较拥有非常多的独立状态(不可共享的状态),或者对象并不是细粒度的,那么就不适合运用享元模式。维持大量的外蕴状态不但会使逻辑复杂而且并不能节约资源。  

2.享元工厂中维护了享元实例的列表,同样也需要占用资源,如果享元占用的资源比较小或者享元的实例不是非常多的话(和列表元素数量差不多),那么就不适合使用享元,关键还是在于权衡得失。  

适用性  

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

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

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

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

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

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

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

优点

1.享元模式的优点在于它大幅度地降低内存中对象的数量。

缺点

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

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

总结  

Flyweight模式解决的是由于大量的细粒度对象所造成的内存开销的问题,它在实际的开发中并不常用,但是作为底层的提升性能的一种手段却很有效。

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于步步为营 .NET 设计模式学习笔记 十七、Flyweight(享元模式)的详细内容...

  阅读:37次

CopyRight:2016-2025好得很程序员自学网 备案ICP:湘ICP备09009000号-16 http://haodehen.cn
本站资讯不构成任何建议,仅限于个人分享,参考须谨慎!
本网站对有关资料所引致的错误、不确或遗漏,概不负任何法律责任。
本网站刊载的所有内容(包括但不仅限文字、图片、LOGO、音频、视频、软件、程序等)版权归原作者所有。任何单位或个人认为本网站中的内容可能涉嫌侵犯其知识产权或存在不实内容时,请及时通知本站,予以删除。

网站内容来源于网络分享,如有侵权发邮箱到:kenbest@126.com,收到邮件我们会即时下线处理。
网站框架支持:HDHCMS   51LA统计 百度统计
Copyright © 2018-2025 「好得很程序员自学网
[ SiteMap ]