26享元模式(Flyweight Pattern)

面向对象的代价
    面向对象很好地解决了系统抽象性的问题,同时在大多数情况下,也不会损及系统的性能。但是,在
某些特殊的应用中下,由于对象的数量太大,采用面向对象会给系统带来难以承受的内存开销。比如:
图形应用中的图元等对象、字处理应用中的字符对象等。 
    
                         
 动机(Motivate):
    采用纯粹对象方案的问题在于大量细粒度的对象会很快充斥在系统中,从而带来很高的运行时代价--------主要指内存需求方面的代价。
    如何在避免大量细粒度对象问题的同时,让外部客户程序仍然能够透明地使用面向对象的方式来进行操作?
意图(Intent):
    运用共享技术有效地支持大量细粒度的对象。  -------《设计模式》GOF
 结构(Struct):
                  
适用性:   

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

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 }
58 
59 // "CharactorC"
60 public class CharactorC : Charactor
61 {
62     // Constructor 
63     public CharactorC()
64     {
65         this._symbol = 'C';
66         this._height = 100;
67         this._width = 160;
68         this._ascent = 74;
69         this._descent = 0;
70     }
71 
72     //Method
73     public override void Display()
74     {
75         Console.WriteLine(this._symbol);
76     }
77 }


好,现在类里面剩下的状态都可以共享了,下面我们要做的工作就是控制Charactor类的创建过程,即如果已经存在了“A”字符这样的实例,就不需要再创建,直接返回实例;如果没有,则创建一个新的实例。如果把这项工作交给Charactor类,即Charactor类在负责它自身职责的同时也要负责管理Charactor实例的管理工作,这在一定程度上有可能违背类的单一职责原则,因此,需要一个单独的类来做这项工作,引入CharactorFactory类,结构图如下:
          

 1 // "CharactorFactory"
 2 public class CharactorFactory
 3 {
 4     // Fields
 5     private Hashtable charactors = new Hashtable();
 6 
 7     // Constructor 
 8     public CharactorFactory()
 9     {
10         charactors.Add("A", new CharactorA());
11         charactors.Add("B", new CharactorB());
12         charactors.Add("C", new CharactorC());
13     }
14        
15     // Method
16     public Charactor GetCharactor(string key)
17     {
18         Charactor charactor = charactors[key] as Charactor;
19 
20         if (charactor == null)
21         {
22             switch (key)
23             {
24                 case "A": charactor = new CharactorA(); break;
25                 case "B": charactor = new CharactorB(); break; 
26                 case "C": charactor = new CharactorC(); break;
27                 //
28             }
29             charactors.Add(key, charactor);
30         }
31         return charactor;
32     }
33 }


到这里已经完全解决了可以共享的状态(这里很丑陋的一个地方是出现了switch语句,但这可以通过别的办法消除,为了简单期间我们先保持这种写法)。下面的工作就是处理刚才被我们剔除出去的那些不可共享的状态,因为虽然将那些状态移除了,但是Charactor对象仍然需要这些状态,被我们剥离后这些对象根本就无法工作,所以需要将这些状态外部化。首先会想到一种比较简单的解决方案就是对于不能共享的那些状态,不需要去在Charactor类中设置,而直接在客户程序代码中进行设置,类结构图如下:
              

 1 public class Program
 2 {
 3     public static void Main()
 4     {
 5         Charactor ca = new CharactorA();
 6         Charactor cb = new CharactorB();
 7         Charactor cc = new CharactorC();
 8 
 9         //显示字符
10 
11         //设置字符的大小ChangeSize();
12     }
13 
14     public void ChangeSize()
15     {
16         //在这里设置字符的大小
17     }
18 }


按照这样的实现思路,可以发现如果有多个客户端程序使用的话,会出现大量的重复性的逻辑,用重构的术语来说是出现了代码的坏味道,不利于代码的复用和维护;另外把这些状态和行为移到客户程序里面破坏了封装性的原则。再次转变我们的实现思路,可以确定的是这些状态仍然属于Charactor对象,所以它还是应该出现在Charactor类中,对于不同的状态可以采取在客户程序中通过参数化的方式传入。类结构图如下:
       
 

  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 SetPointSize(int size);
 19     public abstract void Display();
 20 }
 21 
 22 // "CharactorA"
 23 public class CharactorA : Charactor
 24 {
 25     // Constructor 
 26     public CharactorA()
 27     {
 28         this._symbol = 'A';
 29         this._height = 100;
 30         this._width = 120;
 31         this._ascent = 70;
 32         this._descent = 0;
 33     }
 34 
 35     //Method
 36     public override void SetPointSize(int size)
 37     {
 38         this._pointSize = size;
 39     }
 40 
 41     public override void Display()
 42     {
 43         Console.WriteLine(this._symbol +
 44           "pointsize:" + this._pointSize);
 45     }
 46 }
 47 
 48 // "CharactorB"
 49 public class CharactorB : Charactor
 50 {
 51     // Constructor 
 52     public CharactorB()
 53     {
 54         this._symbol = 'B';
 55         this._height = 100;
 56         this._width = 140;
 57         this._ascent = 72;
 58         this._descent = 0;
 59     }
 60 
 61     //Method
 62     public override void SetPointSize(int size)
 63     {
 64         this._pointSize = size;
 65     }
 66 
 67     public override void Display()
 68     {
 69         Console.WriteLine(this._symbol +
 70           "pointsize:" + this._pointSize);
 71     }
 72 }
 73 
 74 // "CharactorC"
 75 public class CharactorC : Charactor
 76 {
 77     // Constructor 
 78     public CharactorC()
 79     {
 80         this._symbol = 'C';
 81         this._height = 100;
 82         this._width = 160;
 83         this._ascent = 74;
 84         this._descent = 0;
 85     }
 86 
 87     //Method
 88     public override void SetPointSize(int size)
 89     {
 90         this._pointSize = size;
 91     }
 92 
 93     public override void Display()
 94     {
 95         Console.WriteLine(this._symbol +
 96           "pointsize:" + this._pointSize);
 97     }
 98 }
 99 
100 // "CharactorFactory"
101 public class CharactorFactory
102 {
103     // Fields
104     private Hashtable charactors = new Hashtable();
105 
106     // Constructor 
107     public CharactorFactory()
108     {
109         charactors.Add("A", new CharactorA());
110         charactors.Add("B", new CharactorB());
111         charactors.Add("C", new CharactorC());
112     }
113        
114     // Method
115     public Charactor GetCharactor(string key)
116     {
117         Charactor charactor = charactors[key] as Charactor;
118 
119         if (charactor == null)
120         {
121             switch (key)
122             {
123                 case "A": charactor = new CharactorA(); break;
124                 case "B": charactor = new CharactorB(); break; 
125                 case "C": charactor = new CharactorC(); break;
126                 //
127             }
128             charactors.Add(key, charactor);
129         }
130         return charactor;
131     }
132 }
133 
134 public class Program
135 {
136     public static void Main()
137     {
138         CharactorFactory factory = new CharactorFactory();
139 
140         // Charactor "A"
141         CharactorA ca = (CharactorA)factory.GetCharactor("A");
142         ca.SetPointSize(12);
143         ca.Display();
144         
145         // Charactor "B"
146         CharactorB cb = (CharactorB)factory.GetCharactor("B");
147         ca.SetPointSize(10);
148         ca.Display();
149 
150         // Charactor "C"
151         CharactorC cc = (CharactorC)factory.GetCharactor("C");
152         ca.SetPointSize(14);
153         ca.Display();
154     }
155 }


可以看到这样的实现明显优于第一种实现思路。好了,到这里我们就到到了通过Flyweight模式实现了优化资源的这样一个目的。在这个过程中,还有如下几点需要说明:

1.引入CharactorFactory是个关键,在这里创建对象已经不是new一个Charactor对象那么简单,而必须用工厂方法封装起来。

2.在这个例子中把Charactor对象作为Flyweight对象是否准确值的考虑,这里只是为了说明Flyweight模式,至于在实际应用中,哪些对象需要作为Flyweight对象是要经过很好的计算得知,而绝不是凭空臆想。

3.区分内外部状态很重要,这是享元对象能做到享元的关键所在。

到这里,其实我们的讨论还没有结束。有人可能会提出如下问题,享元对象(Charactor) 在这个系统中相对于每一个内部状态而言它是唯一的,这跟单件模式有什么区别呢?这个问题已经很好回答了,那就是单件类是不能直接被实例化的,而享元类是可 以被实例化的。事实上在这里面真正被设计为单件的应该是享元工厂(不是享元)类,因为如果创建很多个享元工厂的实例,那我们所做的一切努力都是白费的,并 没有减少对象的个数。修改后的类结构图如下:
          

 1 // "CharactorFactory"
 2 public class CharactorFactory
 3 {
 4     // Fields
 5     private Hashtable charactors = new Hashtable();
 6 
 7     private CharactorFactory instance;
 8     // Constructor 
 9     private CharactorFactory()
10     {
11         charactors.Add("A", new CharactorA());
12         charactors.Add("B", new CharactorB());
13         charactors.Add("C", new CharactorC());
14     }
15     
16     // Property
17     public CharactorFactory Instance
18     {
19         get 
20         {
21             if (instance != null)
22             {
23                 instance = new CharactorFactory();
24             }
25             return instance;
26         }
27     }
28 
29     // Method
30     public Charactor GetCharactor(string key)
31     {
32         Charactor charactor = charactors[key] as Charactor;
33 
34         if (charactor == null)
35         {
36             switch (key)
37             {
38                 case "A": charactor = new CharactorA(); break;
39                 case "B": charactor = new CharactorB(); break; 
40                 case "C": charactor = new CharactorC(); break;
41                 //
42             }
43             charactors.Add(key, charactor);
44         }
45         return charactor;
46     }
47 }


.NET框架中的应用:


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

 1 public class Program
 2 {
 3     public static void Main(string[] args)
 4     {
 5         string s1 = "abcd";
 6         string s2 = "abcd";
 7 
 8         Console.WriteLine(Object.ReferenceEquals(s1,s2));
 9 
10         Console.ReadLine();
11     }
12 }


Flyweight实现要点:

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/438646.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql一个事务多个log_MySQL识别一个binlog中的一个事物

MySQL测试版本5.7.14设置GTID_MODEONON(3): Both new and replicated transactions must be GTID transactions(生成的是GTID事物,slave也只能应用GTID事物)设置binlog格式为row模式做如下操作mysql> insert into test values(1,2);Query OK, 1 row affected (0.…

go for 循环遍历数组并排序_10. Go语言流程控制:for 循环

本文原文:http://golang.iswbm.comGithub:http://github.com/iswbm/GolangCodingTimeGo里的流程控制方法还是挺丰富,整理了下有如下这么多种:if - else 条件语句switch - case 选择语句for - range 循环语句goto 无条件跳转语句de…

27代理模式(Proxy Pattern)

直接与间接: 人们对复杂的软件系统常有一种处理手法,即增加一层间接层,从而对系统获得一种更为灵活、 满足特定需求的解决方案。 …

31模板方法(Template Method)

无处不在的Template Method 如果你只想掌握一种设计模式,那么它就是Template Method! 动机(Motivate): 变化 -----是软件设计的永恒主题,如何管理变化带来的复杂性?设计模式的艺术性和复杂度就在于如何 分析,并发现系统中…

bat 脚本清空窗口内容_tomcat9.0启动脚本startup.bat的分析

1、 Apache Tomcat的下载和安装从Apache官网https://tomcat.apache.org/可以下载各种版本的tomcat软件,下载的文件格式可以是zip/tar.gz/exe形式的。如下图所示,在64位windows中使用tomcat,我们可以下载"64-bit Windows.zip",直接解…

33迭代器模式(Iterator Pattern)

动机(Motivate): 在软件构建过程中,集合对象内部结构常常变化各异。但对于这些集合对象,我们希望在不暴露其内部结构的同时,可以让外部客户代码透明地访问其中包含的元素;同时这种“透明遍历”也为“ 同一种算法在多种集合对象上进行操作…

32命令模式(Command Pattern)

耦合与变化: 耦合是软件不能抵御变化灾难的根本性原因。不仅实体对象与实体对象之间存在耦合关系,实体对象与行为操作之间也存在耦合关系。 动机(Mot…

34观察者模式(Observer Pattern)

动机(Motivate):在软件构建 过程中,我们需要为某些对象建立一种“通知依赖关系” --------一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密&#x…

36中介者模式(Mediator Pattern)

依赖关系的转化: 动机(Motivate): 在软件构建过程中,经常会出现多个对象互相关联交互的情况,对象之间常常会维持一种复杂的引用关系,如果遇到一些需求的更改,这种直接的引用关系将面临不断的变化。 在这种情况…

相似理论与模型试验_正交实验下的固液耦合相似材料研究

原标题:基于正交试验的固液耦合相似材料研究摘 要:为了研究矿井突水演化规律,通过正交试验研制出一种能同时满足固体力学与水理性的固液 耦合相似材料,该相似材料以河沙为骨料、水泥和大白粉为胶结剂、液体石蜡和淀粉为调节剂。采用 极差分析…

35解释器模式(Interpreter Pattern)

动机(Motivate): 在软件构建过程中,如果某一特定领域的问题比较复杂,类似的模式不断重复出现,如果使用普通的编程方式来实现将面临非常频繁的变化。 在这种情况下,将特定领域的问题表达为某种文法规则下的句子,…

37职责链模式(Chain of Responsibility Pattern)

动机(Motivate): 在软件构建过程中,一个请求可能被多个对象处理,但是每个请求在运行时只能有一个接受者,如果显示指定,将必不可少地带来请求发送者与接受者的紧耦合。 如何使请求的发送者不需要指定具体的接受…

python3中format函数列表_Python3之字符串格式化format函数详解(上)

173.jpg概述在Python3中,字符串格式化操作通过format()方法或者fstring实现。而相比于老版的字符串格式化方式,format()方法拥有更多的功能,操作起来更加方便,可读性也更强。该函数将字符串当成一个模板,通过传入的参数…

38备忘录模式(Memento Pattern)

对象状态的回溯: 对象状态的变化无端,如何回溯/恢复对象在某个点的状态? 动机: 在软件构建过程中,某些对象的状态在转换过程中,可能由于某种需要,要求程序能够…

39策略模式(Strategy Pattern)

算法与对象的耦合: 对象可能经常需要使用多种不同的算法,但是如果变化频繁,会将类型变得脆弱... 动机: 在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将…

40访问者模式(Visitor Pattern)

类层次结构的变化: 类层次结构中可能经常由于引入新的操作,从而将类型变得脆弱... 动机: 在软件构建过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为(方法),如果直接…

41状态模式(State Pattern)

对象状态影响对象行为: 对象拥有不同的状态,往往会行使不同的行为... 动机: 在软件构建过程中,某些对象的状态如果改变以及其行为也会随之而发生变化,比如文档处于只读状态,其支…

python中空格属于字符吗_举例说明python中空格是属于字符

python中空格属于字符吗?答案是肯定的,空格在Python中也是属于字符的。案例:输入一行字符,分别统计出其中英文字母、空格、数字和其它字符的个数。#!/usr/bin/python# -*- coding: UTF-8 -*-import strings raw_input(input a st…

【转】如何将域中的AD数据导入SharePoint

最近刚装好sharepoint2010,想要研究一下,第一件想做的事就是想把AD中的用户信息导入到SharePoint中。 那现在就来看看我是怎么操作的: 1.打开管理中心 sharepoint是通过“用户配置文件同步服务”来实现同步,所以第一步要开启这个…

Apsara Clouder专项技能认证:实现调用API接口

一.API 简介 1.API 的概念 API(Application Programming Interface应用程序编程接口)是一些预定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码或理解内部工作机制的细节 2.API 的特点 API 是一个明确定义的接口,可以为其…