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

定义:

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


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


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


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()方法,也就是要判断这个对象中的所有属性值是不是相等。这样的话,和直接判断一个字符串相等,时间就差了好几倍,所以,键值尽量使用基本数据类型,这样效率更高一些。



评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值