前端外刊评论 2017-12-16
享元设计模式
享元设计模式(Flyweight Pattern)是23种设计模式中广泛引用的其中一种,主要用在构建缓存对象的时候用到,不管是在Java,还是在Android中,都不可或缺,比如我们常见String字符串以及自定义的View中的TypeArray,线程池或者Message等等都有用到享元模式,共同的特点都是复用了已经存在的细粒度对象,并且在对象不需要的时候回收,方便再次使用,减少系统的消耗。
享元模式图
分析一下构成:●FlyWeight 享元接口或者(抽象享元类),定义共享接口
●ConcreteFlyWeight 具体享元类,该类实例将实现共享
●FlyWeightFactory 享元工厂类,控制实例的创建和共享
●ConcreteConpositeFlyweight 它所代表的对象是不可以共享的,并且可以分解成为多个单纯享元对象的组合。
享元模式内部状态VS外部状态
●内部状态是存储在享元对象内部,一般在构造函数的时候确定了,并且不会随环境改变而改变的状态,因此内部状态可以共享。
●外部状态是随环境改变而改变,外部状态在需要使用时通过客户端传入享元对象,由于是随环境而改变,因此内部无法存储,必须由客户端保存。
举个例子,从上海到北京的高铁来说,这里的内部状态是出发点上海和终点北京组成,这2个是无法改变的,而由于座位分等级,比如一等座,二等座,三等座等,这些价格也不一样,因此无法共享,只能有外部传入,也就是外部自己存储这些不共享的信息。
享元简单实例分析
下面看一个简单的例子:
public interface Travel { void showDetailInfo(int level,int price); }
以从上海到北京的高铁为例子,
public class CrhTravel implements Travel { //这里的出发点和终点是内部状态,是固定的,不会改变,可以共享 public String from; public String to; //等级和价格是外部状态,岁客户端动态变化的,不可共享,接口的参数里面也说明了这一点 public int level; public int price; public CrhTravel(String from, String to) { this.from = from; this.to = to; } @Override public void showDetailInfo(int level, int price) { this.level = level; this.price = price; System.out.println("购买从:" + from + " 到" + to + "的" + level + " 等高铁票+" + " ,价格为:" + price); } }
下面再写一个缓存类,用来构建缓存池,因为在高峰期的时候,不可能每次拿票的时候都重新创建一个对象,这里的缓存池用并发的ConcurrentHashMap实现,以便在多线程状态下能正常工作
public class TravelFactory { private static Map<String, Travel> sTravelMap = new ConcurrentHashMap<>(); public static Travel getTicket(String from, String to) { String key = from + "-" + to; if (sTravelMap.containsKey(key)) { System.out.println("使用缓存==>" + key); return sTravelMap.get(key); } else { System.out.println("创建对象==>" + key); Travel travel = new CrhTravel(from, to); sTravelMap.put(key, travel); return travel; } } }
可以看到,在构建对象的时候,首先查看缓存是否存在key的,如果存在,则直接返回,如果不存在就创建一个新的对象并且存入缓存池里面,下面是简单的测试代码:
public class TravelClient { public static void main(String[] args){ Travel travel= TravelFactory.getTravel("上海","北京"); travel.showDetailInfo(1,300); Travel trave2= TravelFactory.getTravel("上海","北京"); trave2.showDetailInfo(2,200); Travel travel3= TravelFactory.getTravel("上海","北京"); travel3.showDetailInfo(3,100); } }
可以看到,不变的是出发点和终点,而变化的是座位等级和价格,运行结果如图:
在第一次用的时候,缓存为空,则直接创建了对象,然后存储起来,第二次以后就直接取出缓存了,这样就可以大量的复用了这些已经存在的对象了,在Android中,Message作为消息机制的重要成员,这里简单分析一下,我们知道,一般在Android获取一个消息对象的时候,一般有2种写法1. Message message=Message.obtain(); 2. Message message=new Message();
我相信大家都是用第一种来获取的,因为里面用到的正是这种享元模式来共享对象的,来看看第一种的源代码:
public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); }
可以看到,里面有一个sPool,如果sPool为空,则直接new一个出来,不过sPool类型也是Message,并不是什么池,但是Message的一个字段next居然也是一个Message,可见Message使用的不是我们一般常见的Map,而是一个链表,这个next就指向了下一个Message,如图所示:
可以看到是一个链表链接起来的,next指向了下一个可以用的Message,而最后一个Message的next指向的是null,这样通过next就链接成了一个可以用的对象池了,我们在刚才获取的方法中看到了,在创建的时候如果没有缓冲,是直接new一个出来的,那么是什么时候放进去的呢,是在回收的时候放进去的,下面是代码:
public void recycle() { if (isInUse()) {//消息还在使用,抛出异常 if (gCheckRecycle) { throw new IllegalStateException("This message cannot be recycled because it " + "is still in use."); } return; } //清空消息,并且添加到消息池中 recycleUnchecked(); } void recycleUnchecked() { // Mark the message as in use while it remains in the recycled object pool. // Clear out all other details. flags = FLAG_IN_USE; what = 0; arg1 = 0; arg2 = 0; obj = null; replyTo = null; sendingUid = -1; when = 0; target = null; callback = null; data = null; //这里放进池里面了 synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this; sPoolSize++; } } }
直到这里已经明白了,Message是在回收消息的时候清空一些标记为并且把消息放进池里面的,这样的话,当我们再次获取的时候,由于池里面已经有缓存了,因此直接获取缓存,然后在回收的时候又把消息清空掉标记放进池里面,如此反反复复的利用已经存在的对象,利用这个原理,我们可以设计简单的缓存池。
设计简单的缓存池
我们已经知道了Message的缓存了,其实利用这种原理我们也可以设计一个类似的池,只不过这里的池用Map来保存,类的设计跟Message类似的,下面是代码:
首先是池的设计,考虑到了普通的单线程和多线程 public final class MessagePools { private MessagePools() throws Exception { throw new IllegalAccessException("Can not access default"); } static interface Pool<T> { /** * 获取缓存的对象 * * @return */ T acquire(); /** * 释放消息对象 * * @param mInstance * @return */ boolean release(T mInstance); /** * 是否在缓存池中 * * @param instance * @return */ boolean isInPool(T instance); /** * 获取缓存池的大小 * @return */ int getPoolSize(); } public static class SimplePools<T> implements Pool<T> { protected final Object[] mPoolObjects; protected int mPoolSize; public SimplePools(int poolSize) { if (poolSize < 0) { throw new IllegalArgumentException("The poolSize is must >0"); } mPoolSize = poolSize; mPoolObjects = new Object[mPoolSize]; } @Override public T acquire() { if (mPoolSize > 0) { final int poolIndex = mPoolSize - 1; T mInstance = (T) mPoolObjects[poolIndex]; mPoolObjects[poolIndex] = null; mPoolSize--; return mInstance; } return null; } @Override public boolean release(T mInstance) { if (isInPool(mInstance)) { throw new IllegalStateException("mInstance is Already in the pool!"); } if (mPoolSize < mPoolObjects.length) { mPoolObjects[mPoolSize] = mInstance; mPoolSize++; return true; } return false; } @Override public boolean isInPool(T instance) { for (int i = 0; i < mPoolSize; i++) { if (mPoolObjects[i] == instance) { return true; } } return false; } @Override public int getPoolSize() { return mPoolSize; } } /** * 并发情况下的缓存池 * @param <T> */ public static class SynchronizedPool<T> extends SimplePools<T>{ private final Object mLock=new Object(); public SynchronizedPool(int poolSize) { super(poolSize); } @Override public boolean release(T mInstance) { synchronized (mLock){ return super.release(mInstance); } } @Override public T acquire() { synchronized (mLock){ return super.acquire(); } } } } 然后是模拟消息的设计 public class MessageObj implements Serializable { private static final MessagePools.SynchronizedPool<MessageObj> sPools = new MessagePools.SynchronizedPool<>(30); //The MessageObj time,The default is currentTime public long when; //MessageObj what public int what; //Message Obj public Object obj; public long getWhen() { return when; } public int getWhat() { return what; } public Object getObj() { return obj; } public static MessageObj obtain(int what, Object obj) { MessageObj m = obtain(); m.what = what; m.obj = obj; return m; } private static MessageObj obtain() { MessageObj messageObj = sPools.acquire(); if (messageObj == null) { messageObj = new MessageObj(); } messageObj.when = System.currentTimeMillis(); return messageObj; } public void recycle() { what = 0; obj = null; what = 0; try { sPools.release(this); } catch (Exception e) { e.printStackTrace(); } } public MessageObj clone(MessageObj src) { MessageObj messageObj = new MessageObj(); messageObj.when = src.when; messageObj.obj = src.obj; messageObj.what = src.what; return messageObj; } }
可以看到,池主要的方法为存入和获取,而模拟消息的方法跟Message获取的方法类似,只是相对简单,字段少了,同样也是在recycler中进行释放然后存储在池里面的,下面来简单测试一下:
Handler handler=new Handler(Looper.getMainLooper()){ @Override public void handleMessage(Message msg) { if (msg.what==0x1234){ MessageObj messageObj= (MessageObj) msg.obj; Log.d("[app]","obj="+messageObj.toString()); } super.handleMessage(msg); } }; String test="测试1"; MessageObj messageObj=MessageObj.obtain(0x1234,test); Message message=handler.obtainMessage(messageObj.getWhat(),messageObj); handler.sendMessage(message); 运行结果为:obj=MessageObj{when=1513368146482, what=4660, obj=测试1}
可以看到程序正常运行,跟Message类似的原理,当然了,池可以添加额外的功能,这个有时间可以去多思考,实际上享元模式更像是一个缓存的对象池,Message并不是享元模式的经典应用,因为并没有内部,外部状态,但我们更应该关注的是对象池的思想来思考和解决问题,灵活应用才是最终的目的。
享元模式总结
享元模式比较简单,但是在一些特定的场合下能发挥重要的作用,可以减少很多不必要对象的创建,降低程序的内存利用,增强了程序的性能,不过也提高了系统的复杂性,特别是内外状态的分离,而外部状态由客户端保存,共享对象读取外部状态的开销可能比较大.
今天的文章就写到这里,感觉大家阅读。