原文:http://blog.csdn.net/hackerain/article/details/7568259
定义:
使用共享对象可以有效的支持大量的细粒度的对象。
这里出现两个名词:共享对象和细粒度对象,所谓共享对象,这个好说,就是这个对象是常驻在内存中的,供大家一起使用,细粒度对象是说对象很多而且这些对象性质很相近,可以提取出很多公共的属性,那么这样来说,细粒度对象的属性就可以分为两种了,一种是可以共享的,称作外部状态,一种是不可以共享的,称作内部状态(我觉得《设计模式之禅》作者这里说的有点不对啊,好像把这个概念给弄反了)。当这种细粒度对象很多时,相同属性重复也很高,造成了内存的浪费,而且还有可能造成内存溢出,所以要将其中的相同属性作为所有细粒度对象共享的,这样就可以控制细粒度对象的数量,从而也就节省了内存,这就是享元模式。享元模式是一个特殊的工厂模式。
在这里举一个例子,是一个驾校网站,有一个在线预约的功能,预约信息包括姓名、电话、预约教练、预约科目(还有其他一些,这里为了简便,就不写了),其中,姓名和电话是每一个用户都不同的,属于内部状态,但是预约教练和预约科目是有固定的一个集合的,是用户可以共享的,属于外部状态,在这里预约采用享元模式来处理,其静态类图如下所示:
ReservFactory类用来生成AbstractReservInfo对象,并使用reservCoach和reservCourse作为键值,如果在集合中已经存在键值标志的对象,则直接从对象池中取出,否则就新建一个对象,并放到池中。享元模式的使用一般是用来解决性能问题的,当有很大的并发量时,如果不使用享元模式共享一部分对象,那么程序要创建很多相似的对象,这不仅是对内存的一种浪费,还大大降低了系统的性能。
享元模式的优点和缺点:
可以大大减少应用程序创建的对象,减少系统内存的占用,增强系统的性能,但是这也增加了系统的复杂性,将对象的属性分为内部状态和外部状态,外部状态具有固化型,不随内部状态的改变而改变,导致系统逻辑较为混乱。
上面例子的源代码:
- public abstract class AbstractReservInfo {
- private String telephone;//电话
- private String reservCoach;//预约教练
- private String reservCourse;//预约科目
- //必须要有用共享的成员变量建造对象的构造函数
- public AbstractReservInfo(String reservCoach, String reservCourse) {
- this.reservCoach = reservCoach;
- this.reservCourse = reservCourse;
- }
- public String getTelephone() {
- return telephone;
- }
- public void setTelephone(String telephone) {
- this.telephone = telephone;
- }
- public String getReservCoach() {
- return reservCoach;
- }
- public void setReservCoach(String reservCoach) {
- this.reservCoach = reservCoach;
- }
- public String getReservCourse() {
- return reservCourse;
- }
- public void setReservCourse(String reservCourse) {
- this.reservCourse = reservCourse;
- }
- }
- public class VIPReservInfo extends AbstractReservInfo {
- private String username;//用户名
- private String password;//密码
- public VIPReservInfo(String reservCoach, String reservCourse) {
- super(reservCoach, reservCourse);
- }
- public String getUsername() {
- return username;
- }
- public void setUsername(String username) {
- this.username = username;
- }
- public String getPassword() {
- return password;
- }
- public void setPassword(String password) {
- this.password = password;
- }
- @Override
- public String toString() {
- return this.getUsername()+" "+this.getPassword()+" "+this.getTelephone()+" "+
- this.getReservCoach()+" "+this.getReservCourse();
- }
- }
- public class VistorReservInfo extends AbstractReservInfo {
- private String realname;//真实姓名
- public VistorReservInfo(String reservCoach, String reservCourse) {
- super(reservCoach, reservCourse);
- }
- public String getRealname() {
- return realname;
- }
- public void setRealname(String realname) {
- this.realname = realname;
- }
- @Override
- public String toString() {
- return this.getRealname()+" "+this.getTelephone()+" "+
- this.getReservCoach()+" "+this.getReservCourse();
- }
- }
- public class VIPReservFactory{
- //池容器
- private static HashMap<String,VIPReservInfo> pool=new HashMap<String,VIPReservInfo>();
- public static VIPReservInfo getVIPReservInfo(String reservCoach, String reservCourse) {
- //将两个字符串连接起来作为一个对象的键值,这样做是为了扩大池中不同类型对象的数量
- String key=reservCoach+reservCourse;
- VIPReservInfo result=null;
- if(!pool.containsKey(key)){
- System.out.println(key+"\t建立对象,并放到vip池中...");
- result=new VIPReservInfo(reservCoach, reservCourse);
- pool.put(key, result);
- }
- else{
- result=pool.get(key);
- System.out.println(key+"\t直接从vip池中获得对象...");
- }
- return result;
- }
- }
- public class VistorReservFactory{
- //池容器
- private static HashMap<String,VistorReservInfo> pool=new HashMap<String,VistorReservInfo>();
- public static VistorReservInfo getVistorReservInfo(String reservCoach, String reservCourse) {
- //将两个字符串连接起来作为一个对象的键值,这样做是为了扩大池中不同类型对象的数量
- String key=reservCoach+reservCourse;
- VistorReservInfo result=null;
- if(!pool.containsKey(key)){
- System.out.println(key+"\t建立对象,并放到vistor池中...");
- result=new VistorReservInfo(reservCoach, reservCourse);
- pool.put(key, result);
- }
- else{
- result=pool.get(key);
- System.out.println(key+"\t直接从vistor池中获得对象...");
- }
- return result;
- }
- }
- public class Client {
- public static void main(String[] args) {
- //VIP预约对象的创建
- VIPReservInfo vip1=VIPReservFactory.getVIPReservInfo("教练1", "科目1");
- vip1.setUsername("suo");
- vip1.setPassword("123");
- vip1.setTelephone("88888888888");
- System.out.println("vip1 Info:\t"+vip1.toString()+"\n");
- //直接从vip池容器中取得对象
- VIPReservInfo vip2=VIPReservFactory.getVIPReservInfo("教练1", "科目1");
- vip1.setUsername("piao");
- vip1.setPassword("321");
- vip1.setTelephone("99999999999");
- System.out.println("vip2 Info:\t"+vip2.toString()+"\n");
- //游客预约对象的创建
- VistorReservInfo vistor1=VistorReservFactory.getVistorReservInfo("教练1", "科目1");
- vistor1.setRealname("索广宇");
- vistor1.setTelephone("77777777777");
- System.out.println("vistor1 Info:\t"+vistor1.toString()+"\n");
- //直接从游客池容器中取得对象
- VistorReservInfo vistor2=VistorReservFactory.getVistorReservInfo("教练1", "科目1");
- vistor2.setRealname("大头");
- vistor2.setTelephone("99999999999");
- System.out.println("vistor2 Info:\t"+vistor2.toString()+"\n");
- }
- }
- 教练1科目1 建立对象,并放到vip池中...
- vip1 Info: suo 123 88888888888 教练1 科目1
- 教练1科目1 直接从vip池中获得对象...
- vip2 Info: piao 321 99999999999 教练1 科目1
- 教练1科目1 建立对象,并放到vistor池中...
- vistor1 Info: 索广宇 77777777777 教练1 科目1
- 教练1科目1 直接从vistor池中获得对象...
- vistor2 Info: 大头 99999999999 教练1 科目1
解释:刚开始是想将两个工厂类合并为一个,使用共同的一个对象池,但是那样做是不行的,因为对象有两种,虽然他们有共同的父类,并且有共同的外部状态,但是正因为是这样,才不能放在一起,假如用一个外部状态,比如上例中的“教练1 科目1”,生成了一个VIPReservedInfo对象,把这个对象存到了对象池中,但是如果游客预约也要使用这个键值的对象,但是此时,这个键值已经被VIPReservedInfo的对象所占用了,此时得到的是VIPReservedInfo对象,并不是想要的VistorReservedInfo对象,所以还是将他们分成两个类,用两个对象池,分别存储,这样看起来似乎还满足单一职责原则。
需要注意的事项:
1、线程安全问题
因为是享元模式是用在高并发环境下,用以解决内存占用的问题,所以一定要考虑线程安全的问题,为什么上例中使用reservCoach和reservCourse两个连接起来的字符串作为键值,而不使用单个的reservCoach或者是reservCourse来作为键值呢?这是因为假如说以单个的reservCourse来做为键值,那么由于科目可能就只有几个,则在对象池中的对象,也就只有几个,想想在高并发的环境下,只有几个对象,那能周转过来吗?肯定会发生线程不安全的问题。所以,对象池中的对象应该尽量多。
2、性能问题
还有一个问题就是,为什么不直接把外部状态的属性封装成一个类,对这个类的对象作为键值,这样操作岂不更方便些?这样当然是可以的,但是有一个性能的问题在这里,判断一个对象相等,要重写这个类的equals()和hashCode()方法,也就是要判断这个对象中的所有属性值是不是相等。这样的话,和直接判断一个字符串相等,时间就差了好几倍,所以,键值尽量使用基本数据类型,这样效率更高一些。
享元模式使用场景:
1、系统存在大量相似对象
2、细粒度对象都具备较接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定身份
3、需要缓冲池的场景