尝试从缓存中获取数据如果数据存在则返回否则从数据源中获取数据放入缓存然后返回
您是否熟悉上面这段逻辑说明?如果您的应用中大量使用了缓存则上面这段逻辑很可能会出现许多次例如
CacheManager cacheManager = new CacheManager(); public List<User> GetFriends(int userId){ string cacheKey = friends_of_user_ + userId; object objResult = cacheManagerGet(cacheKey); if (objResult != null) return (List<User>)objResult; List<User> result = new UserService()GetFriends(userId); cacheManagerSet(cacheKey result); return result;}
这段逻辑似乎比较简单不过在实际应用中从数据源中获取数据可能不是简单地调用一个方法而是需要多个类之间的协作事务控制等等而缓存的读写可能也会比上面的示例来的复杂因此一个可读性高的做法是提供三个独立的方法(读取缓存读取数据源写入缓存)使得一个拥有缓存的方法只需要简单地实现上面所提到的读写逻辑即可
正如文章开头所说如果您的应用中大量使用了缓存则上面这段逻辑很可能会出现许多次在一定程度上这种重复也是多余的违背了DRY原则因此我们设法提供一个基类把这段缓存读写逻辑封装起来
public abstract class CacheReader<T>{ /// <summary>从缓存中获取数据</summary> /// <param name=data>从缓存中取得的数据</param> /// <returns>从缓存中成功取得数据则返回true反之则false</returns> public abstract bool GetFromCache(out T data); /// <summary>从数据源获取数据</summary> /// <returns>从数据源取得的对象</returns> public abstract T ReadFromSource(); /// <summary>将数据写入缓存</summary> /// <param name=data>将要写入缓存的数据</param> public abstract void SetToCache(T data); public T Read() { T data; if (thisGetFromCache(out data)) return data; data = thisReadFromSource(); thisSetToCache(data); return data; }}
于是我们将这段缓存读写逻辑集中到了CacheReader类的Read方法中
而对于每个缓存读写操作我们只要实现一个CacheReader类的子类提供三个抽象方法的具体实现即可如下
private class GetFriendCacheReader : CacheReader<List<User>>{ private int m_userId; private string m_cacheKey; private CacheManager m_cacheManager; public GetFriendCacheReader(int userId CacheManager cacheManager) { thism_userId = userId; thism_cacheKey = friends_of_user_ + userId; thism_cacheManager = cacheManager; } public override bool GetFromCache(out List<User> data) { object objData = thism_cacheManagerGet(thism_cacheKey); if (objData == null) { data = null; return false; } data = (List<User>)objData; return true; } public override List<User> ReadFromSource() { return new UserService()GetFriends(thism_userId); } public override void SetToCache(List<User> data) { thism_cacheManagerSet(thism_cacheKey data); }}
于是我们的GetFriends方法就可以修改成如下模样
public List<User> GetFriends(int userId){ return new GetFriendCacheReader(userId cacheManager)Read();}
典型的模板方法(Template Method)模式的应用真是……优雅?这是明显的矫枉过正!一个GetFriends方法需要开发一个类而应用中少说也该有几十上百个这样的方法吧?于是乎我们吭哧吭哧地开发了几十上百个CacheReader的子类每个子类需要把所有用到的对象和数据封装进去并且实现三个抽象方法——OMG真可谓OOP到了极致!
还好我们可以使用匿名方法为此我们写一个Helper方法
public static class CacheHelper{ public delegate bool CacheGetter<TData>(out TData data); public static TData Get<TData>( CacheGetter<TData> cacheGetter Func<TData> sourceGetter Action<TData> cacheSetter) { TData data; if (cacheGetter(out data)) { return data; } data = sourceGetter(); cacheSetter(data); return data; }}
委托是个好东西可以作为方法的参数使用而匿名方法的存在让这种方式变得尤其有用例如我们现在就可以
public List<User> GetFriends(int userId){ string cacheKey = friends_of_user_ + userId; return CacheHelperGet( delegate(out List<User> data) // cache getter { object objData = cacheManagerGet(cacheKey); data = (objData == null) ? null : (List<User>)objData; return objData != null; } () => // source getter { return new UserService()GetFriends(userId); } (data) => // cache setter { cacheManagerSet(cacheKey data); }); }
看上去是不是有点古怪?其实习惯了就好这种做法有好处还不少
◆可读性好操作的逻辑被分割在不同block中
◆编程方便能够直接使用方法的参数和外部对象不会有封装的麻烦
◆调试方便设置断点之后可以轻松看出从缓存中读取
从数据源读取和写入缓存的过程
在出现匿名方法之后这种将委托作为参数传入方法的做法其实已经非常普遍了例如在微软推出的并行库中就能使用同样的调用方式
void ParallelCalculate(){ double result = ; object syncObj = new object(); List<int> list = GetIntList(); ParallelFor<double>( listCount () => (index ps) => { int value = list[index]; for (int n = ; n < ; n++) { psThreadLocalState += MathSqrt(value) * MathSin(value); } } (threadResult) => { lock (syncObj) { result += threadResult; } }); ConsoleWriteLine(result = + result);}
您接受这种做法了吗?
您善于使用匿名函数吗?