设计模式

设计模式-享元模式

2020-10-14  本文已影响0人  h2coder

什么是享元模式?什么时候用?

享元模式也叫Flyweight(轻量级),享元模式可以有效地支持大量的细粒度的对象。其实就是对象缓存池的一种实现。大量频繁的回调调用或方法调用,如果这个方法不断的去new对象,内存肯定吃不消。而享元模式的做法是缓存这些对象,下次直接获取,实行复用。

怎么实现享元模式?

享元模式实现消息对象缓存

在聊天功能的实现中(WebSocket),使用RxJava封装WebSocket,需要在WebSocket的消息回调中发送Rx事件,而Rx事件需要发送一个实体(WebSocketInfo),这个实体代表了本次WebSocket消息的内容和一些其他字段,单聊量不大的时候还好,但是如果像微信、QQ这种比较大型的聊天软件的话,群聊消息是非常多的(尤其是红包、刷屏),所以消息回调的次数就会非常多,创建WebSocketInfo实体的次数也越多,内存消耗越大。很容易引起内存抖动和频繁的GC导致界面卡顿和线程挂起。

所以这时候就可以使用享元模式,将WebSocketInfo缓存起来,每次消息回调时,检查对象缓存池里面是否有可用的缓存,有则使用,无则创建,复用WebSocketInfo对象,减少内存开销。

实现步骤

优先,为了日后的拓展,我们的对象缓存池,需要支持任意对象,并且支持自动创建对象和缓存对象。

既然要实现,我们就按照上面的3步实现步骤来:

public class CacheItem<T> implements Serializable {
    private static final long serialVersionUID = -401778630524300400L;
    
    /**
     * 缓存的对象
     */
    private T cacheTarget;
    /**
     * 最近使用时间
     */
    private long recentlyUsedTime;
    
    public CacheItem(T cacheTarget, long recentlyUsedTime) {
        this.cacheTarget = cacheTarget;
        this.recentlyUsedTime = recentlyUsedTime;
    }
    
    //...省略get、set方法
}
public class WebSocketInfo implements Serializable {
    private static final long serialVersionUID = -880481254453932113L;

    //内部状态
    private WebSocket mUrl;
    private WebSocket mWebSocket;
    private String mStringMsg;
    private ByteString mByteStringMsg;
    /**
     * 连接成功
     */
    private boolean isConnect;
    /**
     * 重连成功
     */
    private boolean isReconnect;
    /**
     * 准备重连
     */
    private boolean isPrepareReconnect;

    /**
     * 重置
     */
    public WebSocketInfo reset() {
        this.mUrl = null;
        this.mWebSocket = null;
        this.mStringMsg = null;
        this.mByteStringMsg = null;
        this.isConnect = false;
        this.isReconnect = false;
        this.isPrepareReconnect = false;
        return this;
    }
    
    //省略get、set方法
}

内部状态,Map的Key,我们每个聊天界面是一个WebSocket的连接地址Url,所以外部状态则是这个Url,内部状态则是WebSocketInfo中的字段。如果存在需要多个字段合并才能决定一组对象,就需要组合字段,就有了一定的成本。

  1. 第一步,面向接口编程,设计操作接口类。提供创建缓存、设置缓存对象的最大个数、获取缓存对象、获取缓存对象后的回调钩子方法。
public interface ICachePool<T> {
    /**
     * 创建缓存时回调
     */
    T onCreateCache();

    /**
     * 设置缓存对象的最大个数
     */
    int onSetupMaxCacheCount();

    /**
     * 获取一个缓存对象
     *
     * @param cacheKey 缓存Key,为了应对多个观察者同时获取缓存使用
     */
    T obtain(String cacheKey);

    /**
     * 当获取一个缓存后回调,一般在该回调中重置对象的所有字段
     *
     * @param cacheTarget 缓存对象
     */
    void onObtainCacheAfter(T cacheTarget);
}
  1. 因为每种缓存对象需要一个缓存池Java类,公共逻辑应该抽取出来,所以使用模板模式,建立基类BaseCachePool,并且泛型传入缓存对象类型。每次获取缓存对象时,先判断是否达到最大缓存对象数量,未达到直接创建并设置最近使用时间。按最近使用时间进行从小到大排序(所以最近使用的对象在队尾),如果达到,取出队头数据(肯定是最久没有使用的)。进行复用,并调用onObtainCacheAfter()回调钩子方法给子类。
public abstract class BaseCachePool<T> implements ICachePool<T>, Comparator<CacheItem<T>> {
    /**
     * 缓存池
     */
    private ConcurrentHashMap<String, LinkedList<CacheItem<T>>> mPool;

    public BaseCachePool() {
        mPool = new ConcurrentHashMap<>(8);
    }

    @Override
    public T obtain(String cacheKey) {
        //缓存链
        LinkedList<CacheItem<T>> cacheChain;
        //没有缓存过,进行缓存
        if (!mPool.containsKey(cacheKey)) {
            cacheChain = new LinkedList<>();
        } else {
            cacheChain = mPool.get(cacheKey);
        }
        if (cacheChain == null) {
            throw new NullPointerException("cacheChain 缓存链创建失败");
        }
        //未满最大缓存数量,生成一个实例
        if (cacheChain.size() < onSetupMaxCacheCount()) {
            T cache = onCreateCache();
            CacheItem<T> cacheItem = new CacheItem<>(cache, System.currentTimeMillis());
            cacheChain.add(cacheItem);
            mPool.put(cacheKey, cacheChain);
            return cache;
        }
        //达到最大缓存数量。按最近的使用时间排序,最近使用的放后面,每次取只取最前面(最久没有使用的)
        Collections.sort(cacheChain, this);
        CacheItem<T> cacheItem = cacheChain.getFirst();
        cacheItem.setRecentlyUsedTime(System.currentTimeMillis());
        //重置所有属性
        T cacheTarget = cacheItem.getCacheTarget();
        onObtainCacheAfter(cacheTarget);
        return cacheTarget;
    }

    @Override
    public int compare(CacheItem<T> o1, CacheItem<T> o2) {
        return Long.compare(o1.getRecentlyUsedTime(), o2.getRecentlyUsedTime());
    }
}
  1. 建立具体的缓存对象缓存池WebSocketInfoPool。继承基类BaseCachePool,复写3个方法。onCreateCache()中创建未满缓存数量时的对象。返回最大缓存对象数量。在回调钩子方法中重置对象。
public class WebSocketInfoPool extends BaseCachePool<WebSocketInfo> {

    @Override
    public WebSocketInfo onCreateCache() {
        return new WebSocketInfo();
    }

    @Override
    public int onSetupMaxCacheCount() {
        return 8;
    }

    @Override
    public void onObtainCacheAfter(WebSocketInfo cacheTarget) {
        //重置所有字段
        cacheTarget.reset();
    }
}
  1. 使用
/**
 * WebSocketInfo缓存池
 */
private final WebSocketInfoPool mWebSocketInfoPool;

public WebSocketWorkerImpl(Context context...) {
    //...省略参数设置
    mWebSocketInfoPool = new WebSocketInfoPool();
}

/**
 * 初始化WebSocket
 */
private synchronized void initWebSocket(ObservableEmitter<WebSocketInfo> emitter) {
    if (mWebSocket == null) {
        mWebSocket = mClient.newWebSocket(createRequest(mWebSocketUrl), new WebSocketListener() {
            @Override
            public void onOpen(WebSocket webSocket, Response response) {
                super.onOpen(webSocket, response);
                //发送连接成功消息
                if (!emitter.isDisposed()) {
                    //..省略部分不重要代码
                    emitter.onNext(createConnect(mWebSocketUrl, webSocket));
                }
            }

            @Override
            public void onMessage(WebSocket webSocket, String text) {
                super.onMessage(webSocket, text);
                //发送收到的WebSocket消息
                if (!emitter.isDisposed()) {
                    emitter.onNext(createReceiveStringMsg(mWebSocketUrl, webSocket, text));
                }
            }

            @Override
            public void onClosed(WebSocket webSocket, int code, String reason) {
                super.onClosed(webSocket, code, reason);
                //发送关闭消息
                if (!emitter.isDisposed()) {
                    emitter.onNext(createClose(mWebSocketUrl));
                }
            }
        });
    }
}

//------- 状态复用方法 --------
private WebSocketInfo createConnect(String url, WebSocket webSocket) {
    return mWebSocketInfoPool.obtain(url)
            .setUrl(url)
            .setWebSocket(webSocket)
            .setConnect(true);
}

private WebSocketInfo createReceiveStringMsg(String url, WebSocket webSocket, String stringMsg) {
    return mWebSocketInfoPool.obtain(url)
            .setUrl(url)
            .setConnect(true)
            .setWebSocket(webSocket)
            .setStringMsg(stringMsg);
}

//...省略其他状态创建(都是一样的,就不贴了)

Android中享元模式的应用

Android开发中,Handler我们肯定熟悉,而Handler发送的Message对象复用就使用了享元模式,但是Message对象没用使用特定的缓存池,而是本身有一个next的字段保存下一个Message的引用,行成一个链表,所以Message对象既担任了缓存池的职责,也担任了享元对象,属于简化版的享元模式,虽然并不是最经典的享元模式的实现方法,但是分得太细,有些场景也会一定程度增加复杂度。所以至于是按经典方式实现还是简化版实现,得考虑项目来决定。

总结

上一篇下一篇

猜你喜欢

热点阅读