【漸進】延遲載入機制的簡易實現(上)
我們在軟體設計中常遇到這樣一種場景,一個物件的某個成員,它的載入很耗時,然而又不是總是需要,因為我們不希望它在物件初始化的時候就被載入,而是在它被顯示使用時才去載入。
我們總是建議暴露屬性而不是成員,作用之一便是本文的主題"延遲載入", 屬性的get,set使得我們能夠主動的控制成員的載入。
- public class Test
- {
- private string _property;
- public string Property
- {
- get { return this._property; }
- set { this._property = value; }
- }
- }
以上就是最基本的屬性寫法,.net3.0之後,給瞭如下的語法糖:
- public string Property { get; set; }
然而很多時候,情況遠遠要複雜的多,於是有了如下的實現:
- private object _property;
- public object Property
- {
- get
- {
- if (this._property == null)
- this._property = new object();
- return this._property;
- }
- }
當一個物件足夠變得複雜後,上述程式碼更加使得程式碼臃腫而充斥大量重複的判斷。作為程式設計師的我們總是有那麼一股意識,把重複的抽取出來。於是我們隱約有了一個想法,是不是可以設計一個自動的延遲載入機制來代替這種寫法?判斷是重複的,實際的載入方法也是早已存在的,那麼這個機制要做的就是自動的為物件載入某個成員的資料,而無需去重複的編碼判斷是不是已經載入或是未被載入。
熟悉ORM的朋友對於延遲載入一定不陌生,多數ORM框架均提供了優秀的延遲載入機制。
- [ActiveRecord("C_Article")]
- public class Article : ActiveRecordBase<Article>
- {
- [HasMany(typeof(Tag), Lazy = true)]
- public IList TagList { get; set; }
- }
- using (new SessionScope())
- {
- Article article = Article.Find(1);
- Response.Write(article.TagList.Count);
- }
上述程式碼利用Castel的ActiveRecord框架展示了一個延遲載入的呼叫方式:一個特性(HasMany),一個範圍宣告(SessionScope),就完成了載入,這就是本文將嘗試的設計。本文將先嚐試以較簡易的方式實現以便它可以立刻開始工作,後續篇幅將逐步完善這個設計。
設計之前,先確定一下將要設計的機制的大致使用:不使用代理,AOP等思想對原有類做攔截,不引入特性做為標記,當使用者需要使用延遲載入的屬性時,需在當前程式碼上下文宣告一個非延遲載入區域,形如SessionScope。
首先,這裡所設計的機制不同於ORM框架,這裡的資料並非源於資料庫,所以將由使用者來實現正常的資料載入介面:
- ///
- /// 載入介面
- ///
- ///
加載 的型別 - ///
啟用此載入 的對載型別 - public interface ILoader
- {
- ///
- /// 正常載入
- ///
- ///
- ///
- TOut Load(TIn t);
- }
使用者需要實現如上介面來完成正常的資料載入。
接下來,需要設計一個非延遲載入區域 NotLazyScope:
- ///
- /// 延遲載入區域
- ///
- public class NotLazyScope : IDisposable
- {
- ///
- /// 延遲標記槽
- ///
- private LocalDataStoreSlot _slot;
- ///
- /// 延遲標記槽名稱
- ///
- public static readonly string SLOT_NAME = "NotLazy";
- ///
- /// 宣告延遲載入區域
- ///
- public NotLazyScope()
- {
- this._slot = Thread.GetNamedDataSlot(SLOT_NAME);
- Thread.SetData(this._slot, true);
- }
- ///
- /// 結束?延?加?區域宣告
- ///
- public void End()
- {
- Thread.SetData(this._slot, false);
- }
- #region IDisposable 成員
- void IDisposable.Dispose()
- {
- this.End();
- }
- #endregion
- }
為了實現如SessionScope的語法宣告方式,我們需要在當前的上下文記錄一些標記,這裡利用了執行緒棧的資料槽來實現。
接著要設計主要的載入器,作為這個機制的呼叫入口:
- ///
- /// 一級快取容器
- ///
- internal class PrimaryCache : Dictionary<object, Dictionary<string, object>> { }
以上將作為一個快取容器,後面會發掘出其他用途。
- ///
- /// 延?加?器
- ///
- public class LazyLoader
- {
- ///
- /// 一級快取
- ///
- private static PrimaryCache _pool = new PrimaryCache();
- ///
- /// 執?延?加?
- /// ?宣告瞭NotLazyScope?在宣告有效?圍內將執??延?加?
- ///
- ///
執 ?加?的對?型別 - ///
加 ?的型別 - /// 加?所使用的介面例項
- /// 執?加?的對?例項
- /// 延?加?的屬性名稱
- ///
?回上一次加?結果 - public
static TOut Load
(ILoader loader, TIn t, string property) - where TOut : class, new()
- where TIn : class, new()
- {
- if
(Exist
(t, property)) - return _pool[t][property] as TOut;
- lock (_pool)
- {
- if (!Exist
(t, property)) - {
- if (Lazy())
- return default(TOut);
- TOut result = loader.Load(t);
- if (!_pool.ContainsKey(t))
- _pool.Add(t, new Dictionary<string, object>());
- _pool[t].Add(property, result);
- }
- }
- return Lazy() ? default(TOut) : loader.Load(t);
- }
- ///
- /// 是否存在快取
- ///
- ///
- ///
- ///
- ///
- private static bool
Exist
(TIn t, string property) - {
- return _pool.ContainsKey(t) && _pool[t].ContainsKey(property);
- }
- ///
- /// 是否延?
- ///
- ///
- private static bool Lazy()
- {
- return !Convert.ToBoolean(Thread.GetData(Thread.GetNamedDataSlot(NotLazyScope.SLOT_NAME)));
- }
- }
完成了以上這些就足夠完成本文的設計了。
要想真正使用,還需要先來實現一個ILoader介面:
- ///
- /// 好友加?器
- ///
- internal class UserFriendLoader : ILoader<List<long>, UserInfoEN>
- {
- #region
ILoader
- ,UserInfoEN> 成員
- public List<long> Load(UserInfoEN t)
- {
- return UserBO.GetFriendUserIdList(t.UserId);
- }
- #endregion
- }
現在可以來嘗試使用它了:
- return Lazy.LazyLoader.Load<UserInfoEN, List<long>>(new Lazy.UserFriendLoader(), this, "FriendUserIdList");
- using (new Lazy.NotLazyScope())
- {
- //...
- }
上面的程式碼完成了這個簡易設計,能方便的讓你在現有專案中引入這個機制而不用做大幅度改動。不過對於上述設計所提供的呼叫,可能會讓您覺得使用起來比較麻煩,或許您更傾向於簡單的使用類似特性的語法對屬性進行延遲載入的宣告,在接下來的篇幅將一步步嘗試對設計進行改造,讓它的呼叫更友好,設計更合理。
從延遲機制的實現衍生:您可能會注意到上述實現中,對於延遲載入的屬性,當實際載入被執行一次後,便無需再次執行就直接從快取中獲得,基於此可衍生出一個快取機制,如果您熟悉NHibernate,想必會聯想到NH的多級快取設計,對了,這裡便暗合了這種設計,只不過現在這個機制不是基於資料庫了,從這個快取出發,便可擴充套件出延遲載入機制的一級快取。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-617463/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- javascript實現延遲載入效果JavaScript
- JavaScript實現圖片的延遲載入JavaScript
- 在AngularJS中實現一個延遲載入的DirectiveAngularJS
- 實現簡單延遲佇列和分散式延遲佇列佇列分散式
- 延時 (遲) 操作的 PHP 簡單實現PHP
- JS圖片延遲載入分析及簡單的demoJS
- Mybatis延遲載入、快取MyBatis快取
- Hibernate 延遲載入原理
- 延遲載入 Dex 檔案
- 關於延遲載入,立即載入的問題
- HT for Web的HTML5樹元件延遲載入技術實現WebHTML元件
- EF中延遲載入的那些事
- 基於HTML5樹元件延遲載入技術實現HTML元件
- javascript 非同步模組載入 簡易實現JavaScript非同步
- 影像延遲載入 && 列表圖順序載入
- Java實現配置載入機制Java
- mybatis延遲載入和快取MyBatis快取
- 圖片延遲載入策略(JavaScript)JavaScript
- 【譯】更多關於漸進式圖片載入的實現
- 如何實現 SAP UI5 的 Lazy Loading(延遲載入,懶載入)試讀版UI
- Spring Boot 2.2 中的延遲載入Spring Boot
- JavaScript 中的延遲載入屬性模式JavaScript模式
- mybatis入門基礎(七)----延遲載入MyBatis
- 【c#】分享一個簡易的基於時間輪排程的延遲任務實現C#
- Echo.js – 簡單易用的 JavaScript 圖片延遲載入外掛JSJavaScript
- RabbitMQ實現延遲佇列MQ佇列
- RabbitMQ 實現延遲佇列MQ佇列
- 實現延遲搜尋功能
- 前端效能優化——延遲載入和非同步載入前端優化非同步
- VIM Lazy Load 懶載入/延遲載入技術
- 延遲載入演算法微服務的模型演算法微服務模型
- 專案分享六:圖片的延遲載入
- Golang 實現 RabbitMQ 的延遲佇列GolangMQ佇列
- 基於rabbitmq延遲外掛實現分散式延遲任務MQ分散式
- php 實現golang defer延遲執行(先進後出)PHPGolang
- RAG新突破:塊狀注意力機制實現超低延遲檢索增強
- Angular2+,路由預載入--預先載入延遲模組Angular路由
- [譯] 網速敏感的視訊延遲載入方案