面向对象很好地解决了系统抽象性的问题,同时在大多数情况下,也不会损及系统的性能。但是,在
某些特殊的应用中下,由于对象的数量太大,采用面向对象会给系统带来难以承受的内存开销。比如:
图形应用中的图元等对象、字处理应用中的字符对象等。
动机(Motivate):
采用纯粹对象方案的问题在于大量细粒度的对象会很快充斥在系统中,从而带来很高的运行时代价--------主要指内存需求方面的代价。
如何在避免大量细粒度对象问题的同时,让外部客户程序仍然能够透明地使用面向对象的方式来进行操作?
意图(Intent):
运用共享技术有效地支持大量细粒度的对象。 -------《设计模式》GOF
适用性:
当以下所有的条件都满足时,可以考虑使用享元模式:
1、 一个系统有大量的对象。
2、 这些对象耗费大量的内存。
3、 这些对象的状态中的大部分都可以外部化。
4、 这些对象可以按照内蕴状态分成很多的组,当把外蕴对象从对象中剔除时,每一个组都可以仅用一个对象代替。
5、 软件系统不依赖于这些对象的身份,换言之,这些对象可以是不可分辨的。
满足以上的这些条件的系统可以使用享元对象。最后,使用享元模式需要维护一个记录了系统已有的所有享元的表,而这需要耗费资源。因此,应当在有足够多的享元实例可供共享时才值得使用享元模式。
生活中的例子:
享元模式使用共享技术有效地支持大量细粒度的对象。公共交换电话网(PSTN)是享元的一个例子。有一些资源例如拨号音发生器、振铃发生器和拨号接收器是必须由所有用户共享的。当一个用户拿起听筒打电话时,他不需要知道使用了多少资源。对于用户而言所有的事情就是有拨号音,拨打号码,拨通电话。
代码实现:
Flyweight在拳击比赛中指最轻量级,即“蝇量级”,这里翻译为“享元”,可以理解为共享元对象(细粒度对象)的意思。提到Flyweight模式都会一般都会用编辑器例子来说明,这里也不例外,但我会尝试着通过重构来看待Flyweight模式。考虑这样一个字处理软件,它需要处理的对象可能有单个的字符,由字符组成的段落以及整篇文档,根据面向对象的设计思想和Composite模式,不管是字符还是段落,文档都应该作为单个的对象去看待,这里只考虑单个的字符,不考虑段落及文档等对象,于是可以很容易的得到下面的结构图:
1 // "Charactor"
2 public abstract class Charactor
3 {
4 //Fields
5 protected char _symbol;
6
7 protected int _width;
8
9 protected int _height;
10
11 protected int _ascent;
12
13 protected int _descent;
14
15 protected int _pointSize;
16
17 //Method
18 public abstract void Display();
19 }
20
21 // "CharactorA"
22 public class CharactorA : Charactor
23 {
24 // Constructor
25 public CharactorA()
26 {
27 this._symbol = 'A';
28 this._height = 100;
29 this._width = 120;
30 this._ascent = 70;
31 this._descent = 0;
32 this._pointSize = 12;
33 }
34
35 //Method
36 public override void Display()
37 {
38 Console.WriteLine(this._symbol);
39 }
40 }
41
42 // "CharactorB"
43 public class CharactorB : Charactor
44 {
45 // Constructor
46 public CharactorB()
47 {
48 this._symbol = 'B';
49 this._height = 100;
50 this._width = 140;
51 this._ascent = 72;
52 this._descent = 0;
53 this._pointSize = 10;
54 }
55
56 //Method
57 public override void Display()
58 {
59 Console.WriteLine(this._symbol);
60 }
61 }
62
63 // "CharactorC"
64 public class CharactorC : Charactor
65 {
66 // Constructor
67 public CharactorC()
68 {
69 this._symbol = 'C';
70 this._height = 100;
71 this._width = 160;
72 this._ascent = 74;
73 this._descent = 0;
74 this._pointSize = 14;
75 }
76
77 //Method
78 public override void Display()
79 {
80 Console.WriteLine(this._symbol);
81 }
82 }
好了,现在看到的这段代码可以说是很好地符合了面向对象的思想,但是同时我们也为此付出了沉重的代价,那就是性能上的开销,可以想象,在一篇文档中,字符的数量远不止几百个这么简单,可能上千上万,内存中就同时存在了上千上万个Charactor对象,这样的内存开销是可想而知的。进一步分析可以发现,虽然我们需要的Charactor实例非常多,这些实例之间只不过是状态不同而已,也就是说这些实例的状态数量是很少的。所以我们并不需要这么多的独立的Charactor实例,而只需要为每一种Charactor状态创建一个实例,让整个字符处理软件共享这些实例就可以了。
现在我们看到的A,B,C三个字符是共享的,也就是说如果文档中任何地方需要这三个字符,只需要使用共享的这三个实例就可以了。然而我们发现单纯的这样共享也是有问题的。虽然文档中的用到了很多的A字符,虽然字符的symbol等是相同的,它可以共享;但是它们的pointSize却是不相同的,即字符在文档中中的大小是不相同的,这个状态不可以共享。为解决这个问题,首先我们将不可共享的状态从类里面剔除出去,即去掉pointSize这个状态(只是暂时的J),:
1 // "Charactor"
2 public abstract class Charactor
3 {
4 //Fields
5 protected char _symbol;
6
7 protected int _width;
8
9 protected int _height;
10
11 protected int _ascent;
12
13 protected int _descent;
14
15 //Method
16 public abstract void Display();
17 }
18
19 // "CharactorA"
20 public class CharactorA : Charactor
21 {
22 // Constructor
23 public CharactorA()
24 {
25 this._symbol = 'A';
26 this._height = 100;
27 this._width = 120;
28 this._ascent = 70;
29 this._descent = 0;
30 }
31
32 //Method
33 public override void Display()
34 {
35 Console.WriteLine(this._symbol);
36 }
37 }
38
39 // "CharactorB"
40 public class CharactorB : Charactor
41 {
42 // Constructor
43 public CharactorB()
44 {
45 this._symbol = 'B';
46 this._height = 100;
47 this._width = 140;
48 this._ascent = 72;
49 this._descent = 0;
50 }
51
52 //Method
53 public override void Display()
54 {
55 Console.WriteLine(this._symbol);
56 }
57 }
var cpro_id = "u6885494";