温故知新(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条数据,因此
示例中客户端仅用5条数据演示了逻辑过程,大家可以自行修改为客户端程序,看看实际的内存节省情况。