享元模式(英语:Flyweight Pattern)是一种软件设计模式。它使用共享物件,用来尽可能减少内存使用量以及分享资讯给尽可能多的相似物件;它适合用于当大量物件只是重复因而导致无法令人接受的使用大量内存。通常物件中的部分状态是可以分享。常见做法是把它们放在外部数据结构,当需要使用时再将它们传递给享元。----WIKIPEDIA
个人理解
共享,内存消耗大的时候应考虑对象的共享,共享对象可以减少对象的生成数量,这样可以减少内存的消耗,当一个对象和其他的对象存在共性且内容一致的时候,可以将共有的部分抽取出来进行共享,这样生成的所有对象占用内存的总和会减少,这就体现了共享的重要性。
模式定义
共享模式是支持大量细粒度对象的复用,所以享元模式要求能够共享的对象必须是细粒度对象。所谓享元模式就是运行共享技术有效地支持大量细粒度对象的复用。系统使用少量对象,而且这些都比较相似,状态变化小,可以实现对象的多次复用。
要求细粒度对象,那么不可避免的使得对象数量多且性质相近,那么我们可以将这些对象信息分为两部分,内部状态和外部状态。
内部状态:在享元对象内部不随外界环境改变而改变的共享部分。
外部状态:随着环境的改变而改变,不能够共享的状态就是外部状态。
案例解析
先来看下这个例子,考试报名系统中几个基本的信息,包括报名人员的id和考试的location以及考试科目等等,如果不采用享元模式,那么每个报名的人员过来报名的时候,都会创建一个考试信息相关的对象,那么这种对象对于同一个考场的考生而言应该是一样的,所以如果每个考生都要创建一个对象,加入一千个考生,每个对象28字节,那么将会产生28*1000的内存消耗,这样看消耗不是很大,但是,你要清楚的是实际的生成环境中,一个对象属性较多的时候会比上面28字节(只是假设)大的多,那么可能很多M的空间被占用了,一千个考生,每个考场50人,那么只需20个考场即可,也就是说对象如果采用共享,只需要28B*20,从这里你可以看出差距有多大。
类的结构图
将同一个考场的信息进行共享,使得对象占用的内存大幅度的减少。
public class ExamRoom { private String roomId; private String location; private String subject; // 类的标识 private String key; }
public class SignUpFactory { private static HashMap<String, ExamRoom> pool = new HashMap<String, ExamRoom>(); // 获取考场信息 public static ExamRoom getExamRoomInfo(String key){ ExamRoom room = null; if(!pool.containsKey(key)){ room = new ExamRoom(key); pool.put(key, room); System.out.println("新建对象放入池中" + key); }else{ room = pool.get(key); System.out.println("从pool中读取" + key); } return room; } }
这个例子中将考场信息进行抽取,放入HashMap的池中进行共享,通过key作为唯一的标识,进行区分,这里我们采用了科目和考场混合的字符串拼接作为key进行唯一标识的。
通过这样的设计,应该说大大的减少了不必要的ExamRoom对象的产生,这样的设计,在实际的生产环境中,对于整个系统的稳定性是很有用处的。
案例解析2
假设我要去上学,那么一般都是去找公交车咯,我一看时间,八点中,好,小区门口应该有1058的车了,那么这样的一个流程下来,该怎么去表达呢?先思考一个问题,我在想到公交车的时候,需要想到他的基本信息才对,至少时间应该是知道的,因为知道了时间我才知道这个时间点有这个车次啊对吧,接下来进行一个模拟。
主要代码如下:
public class Bus { private String busNumber; private String dateTime; private String driverId; }
public class BusFactory { private static HashMap<String, Bus> busPool = new HashMap<String, Bus>(); static { busPool.put("10588:00SH100002", new Bus("1058", "8:00", "SH100002")); busPool.put("10589:00SH100003", new Bus("1058", "9:00", "SH100003")); busPool.put("105810:00SH100002", new Bus("1058", "10:00", "SH100002")); busPool.put("105811:00SH100003", new Bus("1058", "11:00", "SH100003")); busPool.put("105812:00SH100002", new Bus("1058", "12:00", "SH100002")); } public static Bus getBus(Date date){ Bus bus = null; String driverId = null; String dateTime = null; // 下面这一段进行判断乘客在获取(date)的时候属于哪一个车次以及其信息 // 假设1058路公交车,这里只模拟一下,这里应该是需要数据库进行查询的 if(! date.after(new Date(new Date().getTime() + 10000))){ driverId = "SH100002"; dateTime = "8:00"; } // 合并该车次的基本信息作为唯一标识 String key = "1058" + dateTime + driverId; // 如果对象池中存在的话 if(!busPool.containsKey(key)){ // 其实这里可以更加简化的,也可以只用key构造bus对象 bus = new Bus("1058", dateTime, driverId); busPool.put(key, bus); System.out.println("新增车次"); } // 如果不存在 else{ System.out.println("从pool中获取"); bus = busPool.get(key); } return bus; } } <span style="font-size:18px;"><strong><span style="font-size:12px;"></span></strong></span>
享元模式优点
1 它能够极大的减少系统中对象的个数。
2 使用了外部状态,外部状态相对独立,不会影响到内部状态,所以享元模式使得享元对象能够在不同的环境被共享。
享元模式缺点
1 享元模式需要区分外部状态和内部状态,使得应用程序在某种程度上来说更加复杂化了。
2 为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。