设计模式学习笔记——享元模式

原文:http://blog.csdn.net/hackerain/article/details/7568259

定义:

使用共享对象可以有效的支持大量的细粒度的对象


这里出现两个名词:共享对象和细粒度对象,所谓共享对象,这个好说,就是这个对象是常驻在内存中的,供大家一起使用,细粒度对象是说对象很多而且这些对象性质很相近,可以提取出很多公共的属性,那么这样来说,细粒度对象的属性就可以分为两种了,一种是可以共享的,称作外部状态,一种是不可以共享的,称作内部状态(我觉得《设计模式之禅》作者这里说的有点不对啊,好像把这个概念给弄反了)。当这种细粒度对象很多时,相同属性重复也很高,造成了内存的浪费,而且还有可能造成内存溢出,所以要将其中的相同属性作为所有细粒度对象共享的,这样就可以控制细粒度对象的数量,从而也就节省了内存,这就是享元模式。享元模式是一个特殊的工厂模式。


在这里举一个例子,是一个驾校网站,有一个在线预约的功能,预约信息包括姓名、电话、预约教练、预约科目(还有其他一些,这里为了简便,就不写了),其中,姓名和电话是每一个用户都不同的,属于内部状态,但是预约教练和预约科目是有固定的一个集合的,是用户可以共享的,属于外部状态,在这里预约采用享元模式来处理,其静态类图如下所示:


ReservFactory类用来生成AbstractReservInfo对象,并使用reservCoach和reservCourse作为键值,如果在集合中已经存在键值标志的对象,则直接从对象池中取出,否则就新建一个对象,并放到池中。享元模式的使用一般是用来解决性能问题的,当有很大的并发量时,如果不使用享元模式共享一部分对象,那么程序要创建很多相似的对象,这不仅是对内存的一种浪费,还大大降低了系统的性能。


享元模式的优点和缺点:

可以大大减少应用程序创建的对象减少系统内存的占用,增强系统的性能,但是这也增加了系统的复杂性,将对象的属性分为内部状态和外部状态,外部状态具有固化型,不随内部状态的改变而改变,导致系统逻辑较为混乱。


上面例子的源代码:

[java]  view plain copy
  1. public abstract class AbstractReservInfo {  
  2.     private String telephone;//电话  
  3.     private String reservCoach;//预约教练  
  4.     private String reservCourse;//预约科目  
  5.       
  6.     //必须要有用共享的成员变量建造对象的构造函数  
  7.     public AbstractReservInfo(String reservCoach, String reservCourse) {  
  8.         this.reservCoach = reservCoach;  
  9.         this.reservCourse = reservCourse;  
  10.     }  
  11.       
  12.     public String getTelephone() {  
  13.         return telephone;  
  14.     }  
  15.     public void setTelephone(String telephone) {  
  16.         this.telephone = telephone;  
  17.     }  
  18.     public String getReservCoach() {  
  19.         return reservCoach;  
  20.     }  
  21.     public void setReservCoach(String reservCoach) {  
  22.         this.reservCoach = reservCoach;  
  23.     }  
  24.     public String getReservCourse() {  
  25.         return reservCourse;  
  26.     }  
  27.     public void setReservCourse(String reservCourse) {  
  28.         this.reservCourse = reservCourse;  
  29.     }  
  30. }  
[java]  view plain copy
  1. public class VIPReservInfo extends AbstractReservInfo {  
  2.       
  3.     private String username;//用户名  
  4.     private String password;//密码  
  5.       
  6.     public VIPReservInfo(String reservCoach, String reservCourse) {  
  7.         super(reservCoach, reservCourse);  
  8.     }  
  9.       
  10.     public String getUsername() {  
  11.         return username;  
  12.     }  
  13.     public void setUsername(String username) {  
  14.         this.username = username;  
  15.     }  
  16.     public String getPassword() {  
  17.         return password;  
  18.     }  
  19.     public void setPassword(String password) {  
  20.         this.password = password;  
  21.     }  
  22.   
  23.     @Override  
  24.     public String toString() {  
  25.         return this.getUsername()+" "+this.getPassword()+" "+this.getTelephone()+" "+  
  26.                 this.getReservCoach()+" "+this.getReservCourse();  
  27.     }  
  28. }  
[java]  view plain copy
  1. public class VistorReservInfo extends AbstractReservInfo {  
  2.       
  3.     private String realname;//真实姓名  
  4.   
  5.     public VistorReservInfo(String reservCoach, String reservCourse) {  
  6.         super(reservCoach, reservCourse);  
  7.     }  
  8.       
  9.     public String getRealname() {  
  10.         return realname;  
  11.     }  
  12.   
  13.     public void setRealname(String realname) {  
  14.         this.realname = realname;  
  15.     }  
  16.   
  17.     @Override  
  18.     public String toString() {  
  19.         return this.getRealname()+" "+this.getTelephone()+" "+  
  20.                 this.getReservCoach()+" "+this.getReservCourse();  
  21.     }  
  22.       
  23.       
  24. }  
[java]  view plain copy
  1. public class VIPReservFactory{  
  2.     //池容器  
  3.     private static HashMap<String,VIPReservInfo> pool=new HashMap<String,VIPReservInfo>();  
  4.       
  5.     public static VIPReservInfo getVIPReservInfo(String reservCoach, String reservCourse) {  
  6.           
  7.         //将两个字符串连接起来作为一个对象的键值,这样做是为了扩大池中不同类型对象的数量  
  8.         String key=reservCoach+reservCourse;  
  9.           
  10.         VIPReservInfo result=null;  
  11.         if(!pool.containsKey(key)){  
  12.             System.out.println(key+"\t建立对象,并放到vip池中...");  
  13.             result=new VIPReservInfo(reservCoach, reservCourse);  
  14.             pool.put(key, result);  
  15.         }  
  16.         else{  
  17.             result=pool.get(key);  
  18.             System.out.println(key+"\t直接从vip池中获得对象...");  
  19.         }  
  20.         return result;  
  21.     }  
  22. }  
[java]  view plain copy
  1. public class VistorReservFactory{  
  2.     //池容器  
  3.     private static HashMap<String,VistorReservInfo> pool=new HashMap<String,VistorReservInfo>();  
  4.   
  5.     public static VistorReservInfo getVistorReservInfo(String reservCoach, String reservCourse) {  
  6.           
  7.         //将两个字符串连接起来作为一个对象的键值,这样做是为了扩大池中不同类型对象的数量  
  8.         String key=reservCoach+reservCourse;  
  9.           
  10.         VistorReservInfo result=null;  
  11.         if(!pool.containsKey(key)){  
  12.             System.out.println(key+"\t建立对象,并放到vistor池中...");  
  13.             result=new VistorReservInfo(reservCoach, reservCourse);  
  14.             pool.put(key, result);  
  15.         }  
  16.         else{  
  17.             result=pool.get(key);  
  18.             System.out.println(key+"\t直接从vistor池中获得对象...");  
  19.         }  
  20.         return result;  
  21.     }  
  22. }  
[java]  view plain copy
  1. public class Client {  
  2.     public static void main(String[] args) {  
  3.           
  4.         //VIP预约对象的创建  
  5.         VIPReservInfo vip1=VIPReservFactory.getVIPReservInfo("教练1""科目1");  
  6.         vip1.setUsername("suo");  
  7.         vip1.setPassword("123");  
  8.         vip1.setTelephone("88888888888");  
  9.         System.out.println("vip1 Info:\t"+vip1.toString()+"\n");  
  10.           
  11.         //直接从vip池容器中取得对象  
  12.         VIPReservInfo vip2=VIPReservFactory.getVIPReservInfo("教练1""科目1");  
  13.         vip1.setUsername("piao");  
  14.         vip1.setPassword("321");  
  15.         vip1.setTelephone("99999999999");  
  16.         System.out.println("vip2 Info:\t"+vip2.toString()+"\n");  
  17.           
  18.         //游客预约对象的创建  
  19.         VistorReservInfo vistor1=VistorReservFactory.getVistorReservInfo("教练1""科目1");  
  20.         vistor1.setRealname("索广宇");  
  21.         vistor1.setTelephone("77777777777");  
  22.         System.out.println("vistor1 Info:\t"+vistor1.toString()+"\n");  
  23.                   
  24.         //直接从游客池容器中取得对象  
  25.         VistorReservInfo vistor2=VistorReservFactory.getVistorReservInfo("教练1""科目1");  
  26.         vistor2.setRealname("大头");  
  27.         vistor2.setTelephone("99999999999");  
  28.         System.out.println("vistor2 Info:\t"+vistor2.toString()+"\n");  
  29.     }  
  30. }  
运行结果如下:

[plain]  view plain copy
  1. 教练1科目1  建立对象,并放到vip池中...  
  2. vip1 Info:  suo 123 88888888888 教练1 科目1  
  3.   
  4. 教练1科目1  直接从vip池中获得对象...  
  5. vip2 Info:  piao 321 99999999999 教练1 科目1  
  6.   
  7. 教练1科目1  建立对象,并放到vistor池中...  
  8. vistor1 Info:   索广宇 77777777777 教练1 科目1  
  9.   
  10. 教练1科目1  直接从vistor池中获得对象...  
  11. vistor2 Info:   大头 99999999999 教练1 科目1  

解释:刚开始是想将两个工厂类合并为一个,使用共同的一个对象池,但是那样做是不行的,因为对象有两种,虽然他们有共同的父类,并且有共同的外部状态,但是正因为是这样,才不能放在一起,假如用一个外部状态,比如上例中的“教练1 科目1”,生成了一个VIPReservedInfo对象,把这个对象存到了对象池中,但是如果游客预约也要使用这个键值的对象,但是此时,这个键值已经被VIPReservedInfo的对象所占用了,此时得到的是VIPReservedInfo对象,并不是想要的VistorReservedInfo对象,所以还是将他们分成两个类,用两个对象池,分别存储,这样看起来似乎还满足单一职责原则。

需要注意的事项:

1、线程安全问题

因为是享元模式是用在高并发环境下,用以解决内存占用的问题,所以一定要考虑线程安全的问题,为什么上例中使用reservCoach和reservCourse两个连接起来的字符串作为键值,而不使用单个的reservCoach或者是reservCourse来作为键值呢?这是因为假如说以单个的reservCourse来做为键值,那么由于科目可能就只有几个,则在对象池中的对象,也就只有几个,想想在高并发的环境下,只有几个对象,那能周转过来吗?肯定会发生线程不安全的问题。所以,对象池中的对象应该尽量多。

2、性能问题

还有一个问题就是,为什么不直接把外部状态的属性封装成一个类,对这个类的对象作为键值,这样操作岂不更方便些?这样当然是可以的,但是有一个性能的问题在这里,判断一个对象相等,要重写这个类的equals()和hashCode()方法,也就是要判断这个对象中的所有属性值是不是相等。这样的话,和直接判断一个字符串相等,时间就差了好几倍,所以,键值尽量使用基本数据类型,这样效率更高一些。


享元模式使用场景:

1、系统存在大量相似对象

2、细粒度对象都具备较接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定身份

3、需要缓冲池的场景

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值