【原创·教程·连载】《Android之大话设计模式》--设计模式之结构型模式 第九章:装饰模式 见MM的家长
<大话设计模式>
本教程说明及版权声明
国士工作室是一支专注于Android平台企业级应用开发的技术团队,致力于做中国最棒的Android应用程序开发机构,提供最棒的Android企业级应用开发培训服务。
企业培训和开发合作官方联系方式:
电话:18610086859
Email:hiheartfirst@gmail.com
QQ:1740415547
国士工作室 有你更美好!
l 该文档参考和使用了网络上的免费开放的图片和内容,并以免费开放的方式发布,希望为移动互联网和智能手机时代贡献绵薄之力!可以随意转载,但不得使用该文档谋利。
l 如果对该文档有任何疑问或者建议,请进入官方博客
http://www.cnblogs.com/guoshiandroid/留言或者直接与国士工作室联系(后附联系方式),我们会慎重参考您的建议并根据需要对本文档进行修改,以造福更多开发者!
l 《大话设计模式》的最新及完整内容会在国士工作室官方博客定期更新,请访问国士工作室博客
http://www.cnblogs.com/guoshiandroid/获取更多更新内容。
享元模式 短信可以这样发
享元模式应用场景举例:
GG每天给MM至少发一条短信,而且每天入睡前是必有一条短信的,往往是一些琐事和一些比较肉麻的情话。开始的一个月,GG还对此是乐不可支,随着时间的推移,那些肉麻的话说了很多遍,自己也觉得厌烦了,而且更让人不可忍耐的是这些肉麻的情话每次都要重复的输入。GG把这一烦心事告诉了自己的好友K,K说,“你这个大傻瓜,怎么不把一些你常用的话存放在你的手机中,这样,要用的时候,直接拿来用就行了”,傻GG一听,顿时觉得醍醐灌顶,于是立即在手机中存放入了“宝贝儿,晚安喔”、“你是我天使”,“宝贝儿,我永远的爱你”等话语。
享元模式解释:
享元模式(Flyweight Pattern)是通过使用共享的方式,达到高效地支持大量的细粒度对象。它的目的就是节省占用的空间资源,从而实现系统性能的改善。
享元的英文是Flyweight,它是一个来自于体育方面的专业用语,在拳击、摔跤和举重比赛中特指最轻量的级别。把这个单词移植到软件工程里面,也是用来表示特别小的对象,即细粒度对象。至于为什么我们把Flyweight翻译为“享元”,可以理解为共享元对象,也就是共享细粒度对象。
英文定义为:Use sharing to support large numbers of fine-grained objects efficiently.
享元模式的UML图:
享元模式涉及以下的角色:
抽象享元(Flyweight)角色: 它是所有具体享元类的超类。为这些类规定出需要实现的公共接口,那些需要外蕴状态(External State)的操作可以通过方法的参数传入。抽象享元的接口使得享元变得可能,但是并不强制子类实行共享,因此并非所有的享元对象都是可以共享的。
具体享元(ConcreteFlyweight)角色:具体享元类实现了抽象享元类所规定的接口。如果有内蕴状态(Internal State)的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享。有时候具体享元类又称为单纯具体享元类,因为复合享元类是由单纯具体享元角色通过复合而成的。
不能共享的具体享元类(UnsharableFlyweight): 不能共享的享元类,又叫做复合享元类。一个复合享元对象是由多个单享元对象组成,这些组成的对象是可以共享的,但是复合享元类本身并不能共享。
享元工厂类(FlyweightFactory): 享元工厂类负责创建和管理享元对象。当一个客户端对象请求一个享元对象的时候,享元工厂需要检查系统中是否已经有一个符合要求的享元对象,如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有适当的享元对象的话,享元工厂角色就应当创建一个新的合适的享元对象。
客户类(Client): 客户类需要自行存储所有享元对象的外蕴状态。
享元模式的UML图如下所示:
享元模式深入分析:
它通过与其他类似对象共享数据来减小内存占用。
享元对象的第一类状态称为内蕴状态(Internal State)。它不会随环境改变而改变,存储在享元对象内部,因此内蕴状态是可以共享的,对于任何一个享元对象来讲,它的值是完全相同的。
享元对象的第二类状态称为外蕴状态(External State)。它会随环境的改变而改变,因此是不可以共享的状态,对于不同的享元对象来讲,它的值可能是不同的。享元对象的外蕴状态必须由客户端保存,在享元对象被创建之后,需要使用的时候再传入到享元对象内部。所以享元的外蕴状态与内蕴状态是两类相互独立的状态,彼此没有关联。
享元模式使用场景分析及代码实现:
在上面的使用场景中,存储在GG手机中的肉麻短信就是具体的享元对象。
UML模型图如下所示:
建立抽象享元角色:
package com.diermeng.designPattern.Flyweight; /* * 抽象享元角色 */ public interface BaseSweetWord { //显示方法 public void display(); }
|
建立具体享元角色:
package com.diermeng.designPattern.Flyweight.impl; import com.diermeng.designPattern.Flyweight.BaseSweetWord;
/* * 具体的享元类 */ public class MySweetWord implements BaseSweetWord{ //具体享元属性 private String mychar;
public MySweetWord() {} //具有实例化属性功能的构造方法 public MySweetWord(String mychar) { this.mychar = mychar; }
//对现实功能的具体实现 public void display() { System.out.println(mychar); } }
|
建立享元工厂类:
package com.diermeng.designPattern.Flyweight.impl;
import java.util.HashMap; import java.util.Map;
import com.diermeng.designPattern.Flyweight.BaseSweetWord;
/* * 享元工厂类 */ public class MySweetWordFactory { //享元对象的集合 private Map<String,BaseSweetWord> pool;
//构造函数 实例化享元对象的集合 public MySweetWordFactory() { pool = new HashMap<String,BaseSweetWord>(); }
//获取享元对象 如果集合中不存在该享元对象 就创建这个对象 public BaseSweetWord getMyCharacter(String character) { BaseSweetWord myChar = pool.get(character); if(myChar == null) { myChar = new MySweetWord(character); pool.put(character, myChar); } return myChar; } } |
最后我们建立测试客户端:
package com.diermeng.designPattern.Flyweight.client;
import com.diermeng.designPattern.Flyweight.BaseSweetWord; import com.diermeng.designPattern.Flyweight.impl.MySweetWordFactory;
/* * 享元模式测试客户端 */ public class FlyweightTest {
public static void main(String[] args) { //创建享元工厂 MySweetWordFactory factory = new MySweetWordFactory();
//从具体的享元工厂中取出相应的MyCharacter BaseSweetWord myChar1 = factory.getMyCharacter("宝贝儿,晚安喔"); BaseSweetWord myChar2 = factory.getMyCharacter("你是我天使"); BaseSweetWord myChar3 = factory.getMyCharacter("宝贝儿,晚安喔"); BaseSweetWord myChar4 = factory.getMyCharacter("宝贝儿,我永远的爱你");
myChar1.display(); myChar2.display(); myChar3.display(); myChar4.display();
if(myChar1 == myChar3) { System.out.println("哈哈,恭喜!我们是同一句话,直接复制就行了!"); } else { System.out.println("哎呀,我们不是同一句话!"); } }
} |
输出的结果如下:
宝贝儿,晚安喔 你是我天使 宝贝儿,晚安喔 宝贝儿,我永远的爱你 哈哈,恭喜!我们是同一句话,直接复制就行了!
|
享元模式的优缺点分析:
优点:
使用享元模式可以降低内存中对象的数量,从而为系统节省大量的内存空间。
缺点:
享元模式使得系统更加复杂,因为为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。而且,由于享元工厂需要维护所有的享元对象,此时,如果要维护的享元对象很多的话,在查找具体的享元对象的时候就要消耗大量的时间,换句话说,享元模式是一种以时间换空间的模式。
享元模式的实际应用简介:
享元模式在一般的项目开发中并不常用,而是常常应用于系统底层的开发,以便解决系统的性能问题。
Java中的String类型就是使用了享元模式。
如果在Java中已经创建了一个字符串对象string1,那么下次再创建相同的字符串string2的时候,系统只是把string2的引用指向string1所引用的具体对象,这就实现了相同字符串在内存中的共享。如果每次执行string1=“abc”操作的时候,都创建一个新的字符串对象的话,那么内存的开销会很大。
如果大家有兴趣的话,可以用下面的程序进行测试,就会知道string1和string2的引用是否一致:
String string1= "爱你一万年,爱你的心永不改变";
String string2= "爱你一万年,爱你的心永不改变";
//“==”用来判断两个对象是否是同一个,equals判断字符串的值是否相等
if( string1 == string2 ){
System.out.println("两者一样");
}else{
System.out.println("两者不一样");
}
程序运行后,输出的结果为“两者一样”,这说明String类的设计采用了享元模式。如果string1的内容发生了变化,比如执行了string1 += "让我们结婚吧!"的语句,那么s1与s2的引用将不再一致。
我们额外的谈一下PHP中String的处理。作为一种弱类型语言,PHP的字符串类型是一种基本类型,不是对象。另外,它的执行方式与Java有明显区别,每一个脚本文件执行开始,将会装入所有需要的资源;执行结束后,又将占用的资源就立即全部释放,所以它基本上不会产生类似的性能问题,它的字符串处理的设计,自然也使用不到享元模式。
温馨提示:
面向对象虽然很好地解决了抽象性的问题,但是对于一个实际运行的软件系统,我们还需要考虑面向对象的代价问题,享元模式解决的就是面向对象的代价问题。享元模式采用对象共享的做法来降低系统中对象的个数,从而降低细粒度对象给系统带来的内存压力。
在具体实现方面,我们要注意对象状态的处理,一定要正确地区分对象的内蕴状态和外蕴状态,这是实现享元模式的关键所在。
享元模式的优点在于它大幅度地降低内存中对象的数量。为了做到这一点,享元模式也付出了一定的代价:
1、享元模式为了使对象可以共享,它需要将部分状态外部化,这使得系统的逻辑变得复杂。
2、享元模式将享元对象的部分状态外部化,而读取外部状态使得运行时间会有所加长。
另外,还有一个比较令人关心的问题:到底系统需要满足什么样的条件才能使用享元模式。对于这个问题,总结出以下几点:
1、一个系统中存在着大量的细粒度对象;
2、这些细粒度对象耗费了大量的内存。
3、这些细粒度对象的状态中的大部分都可以外部化;
4、这些细粒度对象可以按照内蕴状态分成很多的组,当把外蕴对象从对象中剔除时,每一个组都可以仅用一个对象代替。
5、软件系统不依赖于这些对象的身份,换言之,这些对象可以是不可分辨的。
满足以上的这些条件的系统可以使用享元对象。最后,使用享元模式需要维护一个记录了系统已有的所有享元的哈希表,也称之为对象池,而这也需要耗费一定的资源。因此,应当在有足够多的享元实例可供共享时才值得使用享元模式。