上篇文章朋友的评论给了我很大的动力写这个系列的文章,看来大家都比较关注这个系列。为了后续文章做一个铺垫,我在这篇文章的前半部分讲解一下BlogEngine.Net的整体架构,后半部分主要是对于BusinessBase类的分析。

下载源代码以后打开解决方案,我们发现从项目的组织结构上BlogEngine.Net分成两个项目:一个是BlogEngine.Core,顾名思意,它就是BlogEngine.Net的核心逻辑层。所有的业务逻辑和一些功能都在这个项目中体现,实际上这个核心业务层中也有数据访问的一部分,那就是Provider模式。在BlogEngine.Net中,关系数据库或XML等的作用只有一个,那就是存储数据,BlogEngine.Net的业务对象的ID生成是由核心层控制的,而不是用数据存储部分生成的,因为这样可以支持更多的数据源。它不同于很多其他业务系统,数据库里面可能有很多存储过程,触发器,函数等来完成一定的业务运算和数据处理。在BlogEngine.Net中,我们甚至可以使用一个.txt文件来自己开发一个Provider给BlogEngine.Net使用,方法很简单,只要实现BlogProvider(BlogEngine.Net提供),MembershipProvider和RoleProvider就可以了,实际上BlogEngine.Net也在很大程度上利用了.Net本身的经典模型。另外的一个项目是一个站点,主要就是具体的Web实现,但是具体的功能都是调用核心层来完成的。

实际上刚开始看BlogEngine.Net的源代码时我也很难入手,不知道从哪里看起,找不到入口的地方。其实也难怪,官方提供的资料大都是关于使用和开发扩展的,社区里找到的东西也不是自己最想要的。研究了一段时间以后我发现整个BlogEngine.Net都在围绕这BusinessBase这个基类展开,其它的类都是为它提供服务或接收它的消息,例如Provider,Extension等。BusinessBase是所有业务类的基类,里面封装了很多业务类共有的特征。它的子类有:

AuthorProfile:用户的Profile的封装。

Page:这个类实际上是对应着BlogEngine.Net中的一篇静态文章,page和post具体区别不是很重要,感兴趣的朋友可以参照一下官方提供的说明。

Post:在BlogEngine.Net应用最多的一个类,代表作者提交的一篇文章。

  Category:文章分类,一篇文章可以属于多个分类,分类之上还可以有父分类。

    下图是他们的继承关系:

  
附件: BusinessBase.jpg
   

    图中的IPublishable接口我会在以后的文章中做详细的讲解。

从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用来标识对象内部的错误信息,这个接口的实现主要用来定义某个属性的验证规则。
INotifyPropertyChanged用户通知客户端数据发生了改变。主要是对象内部数据发生改变时,对于一些绑定控件数据的同步更新。
IChangeTracking如果对象内部数据发生改变,它用来完成接收了数据的改变,包括更新数据存储等。
IDisposable这个就不用解释了吧,做.Net都知道。

    从以上我们可以看出凡是BusinessBase的子类对象都具有当修改数据时,通过属性的改变对外发出属性改变的通知,并实现INotifyPropertyChanged来通知绑定控件,实现IChangeTracking来更新数据的存储。我们再看一下源代码发现BlogEngine.Net将所有业务对象的数据验证交给了Validation模型来处理,这一点运用的很巧妙,统一了验证模型,我很推荐。

Validation

  1. 1Validation#region Validation
  2. 2
  3. 3        private StringDictionary _BrokenRules = new StringDictionary();
  4. 4
  5. 5        /**//// <summary>
  6. 6        /// Add or remove a broken rule.
  7. 7        /// </summary>
  8. 8        /// <param name="propertyName">The name of the property.</param>
  9. 9        /// <param name="errorMessage">The description of the error</param>
  10. 10        /// <param name="isBroken">True if the validation rule is broken.</param>
  11. 11        protected virtual void AddRule(string propertyName, string errorMessage, bool isBroken)
  12. 12        {
  13. 13            if (isBroken)
  14. 14            {
  15. 15                _BrokenRules[propertyName] = errorMessage;
  16. 16            }
  17. 17            else
  18. 18            {
  19. 19                if (_BrokenRules.ContainsKey(propertyName))
  20. 20                {
  21. 21                    _BrokenRules.Remove(propertyName);
  22. 22                }
  23. 23            }
  24. 24        }
  25. 25
  26. 26        /**//// <summary>
  27. 27        /// Reinforces the business rules by adding additional rules to the
  28. 28        /// broken rules collection.
  29. 29        /// </summary>
  30. 30        protected abstract void ValidationRules();
  31. 31
  32. 32        /**//// <summary>
  33. 33        /// Gets whether the object is valid or not.
  34. 34        /// </summary>
  35. 35        public bool IsValid
  36. 36        {
  37. 37            get
  38. 38            {
  39. 39                ValidationRules();
  40. 40                return this._BrokenRules.Count == 0;
  41. 41            }
  42. 42        }
  43. 43
  44. 44        /**//// /// <summary>
  45. 45        /// If the object has broken business rules, use this property to get access
  46. 46        /// to the different validation messages.
  47. 47        /// </summary>
  48. 48        public virtual string ValidationMessage
  49. 49        {
  50. 50            get
  51. 51            {
  52. 52                if (!IsValid)
  53. 53                {
  54. 54                    StringBuilder sb = new StringBuilder();
  55. 55                    foreach (string messages in this._BrokenRules.Values)
  56. 56                    {
  57. 57                        sb.AppendLine(messages);
  58. 58                    }
  59. 59
  60. 60                    return sb.ToString();
  61. 61                }
  62. 62
  63. 63                return string.Empty;
  64. 64            }
  65. 65        }
  66. 66
  67. 67        #endregion
复制代码

那么,BusinessBase的子类都需要做什么呢?它们需要重写数据存储的操作方法(通过Provider的调用完成,主要是DataSelect等),这也是面向接口编程所提倡的。对于内部数据的处理BusinessBase使用了IsNew,IsChanged和IsDeleted统一了编程模型,并定义了一个SaveAction枚举来实现统一的处理与通知消息的封装。

    BusinessBase提供了两个事件在内部数据进行存储前后触发——保存前事件和保存后事件,用来给外部提供访问点,有点类似于Asp.Net的管道事件的东西,不过这些事件都是类的事件,并非属于某个对象。这样外部可以对于保存前的事件进行处理,也可以对于保存后的事件处理,这种模型很有利于扩展。例如我们可以很轻松的纪录业务日志,而且纪录的很统一。此外,BusinessBase重写了相等的方法或操作符,用于两个对象的排序和比较,这都是业务对象所共有的特性。

    那么,对于派生类从这个基类继承之后我们要实现什么呢,无非就是自己的数据存储方法,数据检验规则,自己的ID类型,还需要大家注意的就是每个派生类都提供了一个静态属性用来提取整个对象列表,还可以根据具体的查询信息获得对象列表,不过他们都属于类的方法,也就是静态方法。对于结构和关系比较复杂的派生类,例如Post包含了Comment列表及其相应的方法用来表示和操作对象之间的关系。此外基类的MarkOld方法用于标识这个对象已经经过了处理,不需要在处理了,子类可以重写这个方法用于提供自己的实现。其余的一些方法或属性都是具体类中所需要的,这个很好分析。

    例如,对于客户端程序我们只要这样就可以完成数据操作:

添加文章:

  1. 1 post = new Post();
  2. 2 post.DateCreated = DataTime.Now;
  3. 3 post.Author = “Guo Xingwang”;
  4. 4 post.Title = “BlogEngine.Net架构与源代码分析系列part2:业务对象——共同的父类BusinessBase”;
  5. 5 post.Content = “xxxxxx”;
  6. 6 post.Description = “xxxxxx”;
  7. 7 post.IsPublished = true;
  8. 8 post.IsCommentsEnabled = false;
  9. 9 post.Categories.Clear();
  10. 10 post.Tags.Clear();
  11. 11 post.Save();
  12. 12
复制代码

修改文章:

  1. 1 post = Post.GetPost(new Guid(Request.QueryString["id"]));
  2. 2 post.Title = “BlogEngine.Net架构与源代码分析系列part2:业务对象——共同的父类BusinessBase”;
  3. 3 post.Content = “xxxxxx”;
  4. 4 post.Description = “xxxxxx”;
  5. 5 post.IsPublished = true;
  6. 6 post.IsCommentsEnabled = false;
  7. 7 post.Save();
  8. 8
复制代码

删除文章:

  1. 1 post = Post.GetPost(new Guid(Request.QueryString["id"]));
  2. 2 2 post.Delete();//实际上在对象内部做了一个标识,当Save时就会根据这个标识来完成具体的删除
  3. 3 3 post.Save();
  4. 4
复制代码

很明显,BlogEngine.Net采用了面向对象的设计方法,事件和继承得到了很广泛的应用,此外它更好的运用了.Net平台自身提供的模型解决问题,例如Provider模型,一些接口规范等。此外我们可以看到,BlogEngine.Net在内存中有很多对象,以空间换取时间的处理方法在这样的系统中还是比较可靠的。

    做一些总结是很有必要的。