上篇文章朋友的評論給了我很大的動力寫這個{
tagshow(event)
}">系列的文章,看來大家都比較關注這個系列。為了後續文章做一個鋪墊,我在這篇文章的前半部分講解一下BlogEngine.Net的整體{
tagshow(event)
}">架構,後半部分主要是對於BusinessBase類的分析。
下載原始碼以後開啟解決方案,我們發現從項目的組織圖上BlogEngine.Net分成兩個項目:一個是BlogEngine.Core,顧名思意,它就是BlogEngine.Net的核心邏輯層。所有的商務邏輯和一些功能都在這個項目中體現,實際上這個核心業務層中也有資料訪問的一部分,那就是Provider模式。在BlogEngine.Net中,關聯式資料庫或XML等的作用只有一個,那就是{
tagshow(event)
}">儲存資料,BlogEngine.Net的業務對象的ID產生是由核心層控制的,而不是用資料存放區部分產生的,因為這樣可以支援更多的資料來源。它不同於很多其他業務系統,資料庫裡面可能有很多預存程序,觸發器,函數等來完成一定的業務運算和資料處理。在BlogEngine.Net中,我們甚至可以使用一個.txt檔案來自己開發一個Provider給BlogEngine.Net使用,方法很簡單,只要實現BlogProvider(BlogEngine.Net提供),MembershipProvider和RoleProvider就可以了,實際上BlogEngine.Net也在很大程度上利用了.Net本身的經典{
tagshow(event)
}">模型。另外的一個項目是一個網站,主要就是具體的Web實現,但是具體的功能都是調用核心層來完成的。
實際上剛開始看BlogEngine.Net的原始碼時我也很難入手,不知道從哪裡看起,找不到入口的地方。其實也難怪,官方提供的資料大都是關於使用和開發擴充的,社區裡找到的東西也不是自己最想要的。研究了一段時間以後我發現整個BlogEngine.Net都在圍繞這BusinessBase這個基類展開,其它的類都是為它提供服務或接收它的訊息,例如Provider,Extension等。BusinessBase是所有業務類的基類,裡面封裝了很多業務類共有的特徵。它的子類有:
AuthorProfile:使用者的Profile的封裝。
Page:這個類實際上是對應著BlogEngine.Net中的一篇靜態文章,page和post具體區別不是很重要,感興趣的朋友可以參照一下官方提供的說明。
Post:在BlogEngine.Net應用最多的一個類,代表作者提交的一篇文章。
Category:文章分類,一篇文章可以屬於多個分類,分類之上還可以有父分類。
是他們的繼承關係:
附件: BusinessBase.jpg
圖中的IPublishable{
tagshow(event)
}">介面我會在以後的文章中做詳細的講解。
從BusinessBase的原型
BusinessBase原型
1 public abstract class BusinessBase<TYPE, KEY> : IDataErrorInfo, INotifyPropertyChanged, IChangeTracking, IDisposable where TYPE : BusinessBase<TYPE, KEY>, new()
2 {}
我們可以看出:
1.BusinessBase是一個泛型類。這個泛型設計的很好,Type用來標識具體的子類類型,Key主要是子類對象的唯一ID,這個ID在AuthorProfile是String類型,而在Page等其它類中是Guid類型,所以定義基類時才會採取這種泛型設定。
2.BusinessBase實現的介面也是微軟推薦的業務對象應該實現的介面。
IDataErrorInfo用來標識對象內部的錯誤資訊,這個介面的實現主要用來定義某個屬性的{
tagshow(event)
}">驗證規則。
INotifyPropertyChanged使用者通知用戶端資料發生了改變。主要是對象內部資料發生改變時,對於一些綁定{
tagshow(event)
}">控制項資料的{
tagshow(event)
}">同步更新。
IChangeTracking如果對象內部資料發生改變,它用來完成接收了資料的改變,包括更新資料存放區等。
IDisposable這個就不用解釋了吧,做.Net都知道。
從以上我們可以看出凡是BusinessBase的子類對象都具有當修改資料時,通過屬性的改變對外發出屬性改變的通知,並實現INotifyPropertyChanged來通知繫結控制項,實現IChangeTracking來更新資料的儲存。我們再看一下原始碼發現BlogEngine.Net將所有業務對象的資料驗證交給了Validation模型來處理,這一點運用的很巧妙,統一了驗證模型,我很推薦。
Validation
- 1Validation#region Validation
- 2
- 3 private StringDictionary _BrokenRules = new StringDictionary();
- 4
- 5 /**//// <summary>
- 6 /// Add or remove a broken rule.
- 7 /// </summary>
- 8 /// <param name="propertyName">The name of the property.</param>
- 9 /// <param name="errorMessage">The description of the error</param>
- 10 /// <param name="isBroken">True if the validation rule is broken.</param>
- 11 protected virtual void AddRule(string propertyName, string errorMessage, bool isBroken)
- 12 {
- 13 if (isBroken)
- 14 {
- 15 _BrokenRules[propertyName] = errorMessage;
- 16 }
- 17 else
- 18 {
- 19 if (_BrokenRules.ContainsKey(propertyName))
- 20 {
- 21 _BrokenRules.Remove(propertyName);
- 22 }
- 23 }
- 24 }
- 25
- 26 /**//// <summary>
- 27 /// Reinforces the business rules by adding additional rules to the
- 28 /// broken rules collection.
- 29 /// </summary>
- 30 protected abstract void ValidationRules();
- 31
- 32 /**//// <summary>
- 33 /// Gets whether the object is valid or not.
- 34 /// </summary>
- 35 public bool IsValid
- 36 {
- 37 get
- 38 {
- 39 ValidationRules();
- 40 return this._BrokenRules.Count == 0;
- 41 }
- 42 }
- 43
- 44 /**//// /// <summary>
- 45 /// If the object has broken business rules, use this property to get access
- 46 /// to the different validation messages.
- 47 /// </summary>
- 48 public virtual string ValidationMessage
- 49 {
- 50 get
- 51 {
- 52 if (!IsValid)
- 53 {
- 54 StringBuilder sb = new StringBuilder();
- 55 foreach (string messages in this._BrokenRules.Values)
- 56 {
- 57 sb.AppendLine(messages);
- 58 }
- 59
- 60 return sb.ToString();
- 61 }
- 62
- 63 return string.Empty;
- 64 }
- 65 }
- 66
- 67 #endregion
複製代碼
那麼,BusinessBase的子類都需要做什麼呢?它們需要重寫資料存放區的操作方法(通過Provider的調用完成,主要是DataSelect等),這也是面向介面編程所提倡的。對於內部資料的處理BusinessBase使用了IsNew,IsChanged和IsDeleted統一了編程模型,並定義了一個SaveAction枚舉來實現統一的處理與通知訊息的封裝。
BusinessBase提供了兩個事件在內部資料進行儲存前後觸發——儲存前事件和儲存後事件,用來給外部提供訪問點,有點類似於Asp.Net的管道事件的東西,不過這些事件都是類的事件,並非屬於某個對象。這樣外部可以對於儲存前的事件進行處理,也可以對於儲存後的事件處理,這種模型很有利於擴充。例如我們可以很輕鬆的紀錄業務日誌,而且紀錄的很統一。此外,BusinessBase重寫了相等的方法或操作符,用於兩個對象的排序和比較,這都是業務對象所共有的特性。
那麼,對於衍生類別從這個基類繼承之後我們要實現什麼呢,無非就是自己的資料存放區方法,資料檢驗規則,自己的ID類型,還需要大家注意的就是每個衍生類別都提供了一個靜態屬性用來提取整個對象列表,還可以根據具體的查詢資訊獲得對象列表,不過他們都屬於類的方法,也就是靜態方法。對於結構和關係比較複雜的衍生類別,例如Post包含了Comment列表及其相應的方法用來表示和操作對象之間的關係。此外基類的MarkOld方法用於標識這個對象已經經過了處理,不需要在處理了,子類可以重寫這個方法用於提供自己的實現。其餘的一些方法或屬性都是具體類中所需要的,這個很好分析。
例如,對於用戶端程式我們只要這樣就可以完成資料操作:
添加文章:
- 1 post = new Post();
- 2 post.DateCreated = DataTime.Now;
- 3 post.Author = “Guo Xingwang”;
- 4 post.Title = “BlogEngine.Net架構與原始碼分析系列part2:業務對象——共同的父類BusinessBase”;
- 5 post.Content = “xxxxxx”;
- 6 post.Description = “xxxxxx”;
- 7 post.IsPublished = true;
- 8 post.IsCommentsEnabled = false;
- 9 post.Categories.Clear();
- 10 post.Tags.Clear();
- 11 post.Save();
- 12
複製代碼
修改文章:
- 1 post = Post.GetPost(new Guid(Request.QueryString["id"]));
- 2 post.Title = “BlogEngine.Net架構與原始碼分析系列part2:業務對象——共同的父類BusinessBase”;
- 3 post.Content = “xxxxxx”;
- 4 post.Description = “xxxxxx”;
- 5 post.IsPublished = true;
- 6 post.IsCommentsEnabled = false;
- 7 post.Save();
- 8
複製代碼
刪除文章:
- 1 post = Post.GetPost(new Guid(Request.QueryString["id"]));
- 2 2 post.Delete();//實際上在對象內部做了一個標識,當Save時就會根據這個標識來完成具體的刪除
- 3 3 post.Save();
- 4
複製代碼
很明顯,BlogEngine.Net採用了物件導向的設計方法,事件和繼承得到了很廣泛的應用,此外它更好的運用了.Net平台自身提供的模型解決問題,例如Provider模型,一些介面規範等。此外我們可以看到,BlogEngine.Net在記憶體中有很多個物件,以空間換取時間的處理方法在這樣的系統中還是比較可靠的。
做一些總結是很有必要的。