温故知新(6)——享元模式

概述

在进行面向对象编程时,我们通常根据经验将领域抽象成大大小小,各种层次的对象。一般情况下这样没有什么问题,但如果某些表示细节的对象非常繁多、成千上万,直接构造这么多的对象就会产生严重的性能问题甚至无法实现。因此我们需要想出某种办法来解决这个问题,可能的一种方式就是通过共享,减少对象的创建,用GOF的话来说就是。

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

上面就是享元模式的意图。想要使用享元模式,面对的问题需要:

1、使用大量的对象,并且这些对象造成了大量的开销;

2、对象的状态可以分为内部状态和外部状态;如果去除外部状态,这些对象可以缩减为较少的数目;

3、由于需要共享,应用程序不依赖对象标识。

所谓的内部状态就是可以共享的通用部分,这部分内容保存在共享对象中;相反外部状态就是可变的部分,由使用者有外部传入。

享元模式,增加了程序查找、传输等方面的开销;同时也是对面向对象封装的一种破坏,因此需要仔细评估,在确信有必要时再将其引入。

结构

享元模式经典的实现结构如下:

享元

首先忽略右下角的UnsharedConcreteFlyweight。看看此模式的参与者。

1、享元对象的抽象,用来定义外部状态的接收与处理——Flyweight;

2、享元对象的实现——ConcreteFlyweight;

3、享元工厂,创建并管理享元对象。当需要一个享元对象时,享元工厂负责创建或者提供一个已创建好的享元对象(以达到共享的目的)——FlyweightFactory;

4、客户端程序,传入外部状态,使用享元对象——Client;

从上面可以看出享元模式的要点是建立一个工厂,该工厂通过保存并返回已创建好的对象的方式达到共享之目的。通常将享元工厂工厂实现为单例。

享元模式经常与组合模式联合使用,图上UnsharedConcreteFlyweight被称为不可共享享元对象会复合享元对象,它通常包含若干普通的享元对象。(这一点参考了博文http://www.cnblogs.com/aspnet2008/archive/2009/02/12/1387230.html中的描述,如果理解不准确,希望大家指出。)

示例

场景描述:假设有一个系统,用来展示某些经济指标数据,这些指标数据具有几种不同的单位,展示依据单位的不同有不同的格式;指标数据每分钟产生一个新数据。

分析:由于指标数据每分钟产生一个,因此如果时间久远将产生非常的多的对象,开销巨大;指标的单位和展示格式的种类较少(只有几个)。因此如果将单位和展示格式作为内部属性共享,而将时间和指标数值作为外部属性传入,将可以避免大量对象的创建,符合享元模式的意图。

1、定义享元对象的抽象,一个表示指标数据的抽象Indicator。

 1:  using System;
 2:   
 3:  namespace DesignPatterns.Flyweight
 4:  {
 5:      /// <summary>
 6:      /// 指标数据的抽象类
 7:      /// </summary>
 8:      public abstract class Indicator
 9:      {
10:          protected string unit;
11:   
12:          /// <summary>
13:          /// 单位
14:          /// </summary>
15:          public string Unit
16:          {
17:              get
18:              {
19:                  return unit;
20:              }
21:          }
22:   
23:          protected string displayFormat;
24:   
25:          /// <summary>
26:          /// 展示格式
27:          /// </summary>
28:          public string DisplayFormat
29:          {
30:              get
31:              {
32:                  return displayFormat;
33:              }
34:          }
35:   
36:          //默认展示
37:          public virtual void Display(DateTime date, decimal value)
38:          {
39:              Console.WriteLine(date.ToString("yyyy-MM-dd HH:mm") + " " + value.ToString(this.DisplayFormat) + "(" + this.Unit + ")");
40:          }
41:      }
42:  }
43:   

2、几个享元对象的具体实现。

“元”为单位的指标数据YuanIndicator。

 1:  using System;
 2:   
 3:  namespace DesignPatterns.Flyweight
 4:  {
 5:      /// <summary>
 6:      /// “元”指标数据
 7:      /// </summary>
 8:      public class YuanIndicator : Indicator
 9:      {
10:          public YuanIndicator()
11:          {
12:              this.unit = "元";
13:              this.displayFormat = "0.00";
14:          }
15:      }
16:  }
17:   

“万元”为单位的指标数据WanYuanIndicator。

 1:  using System;
 2:   
 3:  namespace DesignPatterns.Flyweight
 4:  {
 5:      /// <summary>
 6:      /// “万元”指标
 7:      /// </summary>
 8:      public class WanYuanIndicator : Indicator
 9:      {
10:          public WanYuanIndicator()
11:          {
12:              this.unit = "万元";
13:              this.displayFormat = "0";
14:          }
15:      }
16:  }
17:   

“百分比”为单位的指标数据PercentIndicator。百分比的展示与其他的不同,这里覆盖了父类的展示方法。

 1:  using System;
 2:   
 3:  namespace DesignPatterns.Flyweight
 4:  {
 5:      /// <summary>
 6:      /// “百分比”指标
 7:      /// </summary>
 8:      public class PercentIndicator : Indicator
 9:      {
10:          public PercentIndicator()
11:          {
12:              this.unit = "%";
13:              this.displayFormat = "0.00";
14:          }
15:   
16:          //百分比的特殊展示
17:          public override void Display(DateTime date, decimal value)
18:          {
19:              Console.WriteLine(date.ToString("yyyy-MM-dd HH:mm") + " " + value.ToString(this.DisplayFormat) + this.Unit);
20:          }
21:      }
22:  }
23:   

3、享元工厂IndicatorFactory,这里实现了享元工厂的单例化。为了示例的简洁,这里保留的创建时的switch语句。

 1:  using System;
 2:  using System.Collections.Generic;
 3:   
 4:  namespace DesignPatterns.Flyweight
 5:  {
 6:      /// <summary>
 7:      /// 享元工厂
 8:      /// </summary>
 9:      public class IndicatorFactory
10:      {
11:          #region 享元工厂的单例实现
12:          private static IndicatorFactory instance;
13:   
14:          public static IndicatorFactory Instance
15:          {
16:              get
17:              {
18:                  if (instance == null)
19:                  {
20:                      instance = new IndicatorFactory();
21:                  }
22:                  return instance;
23:              }
24:          }
25:   
26:          private IndicatorFactory() { }
27:          #endregion
28:  
29:          private Dictionary<string, Indicator> Indicators;
30:   
31:          //获取享元对象
32:          public Indicator GetIndicator(string name)
33:          {
34:              if (Indicators == null)
35:              {
36:                  Indicators = new Dictionary<string, Indicator>();
37:              }
38:              if (!Indicators.ContainsKey(name))
39:              {
40:                  //这里当然是应该重构的
41:                  switch (name)
42:                  {
43:                      case "Yuan":
44:                          {
45:                              Indicators.Add("Yuan", new YuanIndicator());
46:                              break;
47:                          }
48:                      case "WanYuan":
49:                          {
50:                              Indicators.Add("WanYuan", new WanYuanIndicator());
51:                              break;
52:                          }
53:                      case "Percent":
54:                          {
55:                              Indicators.Add("Percent", new PercentIndicator());
56:                              break;
57:                          }
58:                      default:
59:                          {
60:                              throw new NotSupportedException();
61:                          }
62:                  }
63:              }
64:              return Indicators[name];
65:          }
66:      }
67:  }
68:   

4、客户端代码。

 1:  using System;
 2:  using System.Collections.Generic;
 3:   
 4:  namespace DesignPatterns.Flyweight
 5:  {
 6:      class Program
 7:      {
 8:          static void Main(string[] args)
 9:          {
10:              decimal[] values = new decimal[] { 12.1m, 13.25m, 5.67m, 3.28m, 17.83m };
11:   
12:              DisplayIndicators("Yuan", values);
13:              DisplayIndicators("WanYuan", values);
14:              DisplayIndicators("Percent", values);
15:   
16:              Console.WriteLine("按任意键结束...");
17:              Console.ReadKey();
18:          }
19:   
20:          //展示指一组标数
21:          private static void DisplayIndicators(string indicatorTypeName, decimal[] values)
22:          {
23:              DateTime time = DateTime.Now;
24:              foreach (var v in values)
25:              {
26:                  Indicator indicator = IndicatorFactory.Instance.GetIndicator(indicatorTypeName);
27:                  indicator.Display(time, v);
28:                  time = time.AddMinutes(-1);
29:              }
30:          }
31:      }
32:  }
33:   

5、运行查看结果。示例中我只模拟了5条数据,因此

image

示例中客户端仅用5条数据演示了逻辑过程,大家可以自行修改为客户端程序,看看实际的内存节省情况。

posted @ 2012-09-10 17:12  宽厚  阅读(1272)  评论(2编辑  收藏  举报