来自:http://www.cnblogs.com/daizhj/archive/2008/01/02/1023366.html
通过前几个月的“外围清剿”,我介绍了一些在项目中边边角角的类和项目。当然这种介绍只是前期热身准备。因为从这篇文章开始,本人将跟大家聊一聊关于这个产品架构上面的东西,以备大家对这个产品有一个总体上的认识,从而为后面的文章做一下铺垫。费话少说,开始正文吧:)
首先请大家看一下官方提供的“前台页面层次图”如下: http://nt.discuz.net/doc/Default.aspx?cid=4
当然这张图是简化了许多,但相信看过我们代码的朋友应该很容易明白,我们之前开源的代码就是按这种架构模式
开发的。因为园子里有些朋友可能是最近几个月才开始关注我们的这个产品,所以这里不妨也将我关于上面这张图的理
解一并发上来,请大家留意下面这张图,这是我个人对目前源码(dll)功能划分在MVC模式下的对应位置:
其实这只是一张缩略图。只是想让大家心里先有个数。而图中的discuz.config.dll(配置项),
discuz.data.dll(数据访问项)以及discuz.aggragation.dll(论坛聚合项)是在以前做过介绍的,详情见链接。
这里要重点说明的是discuz.forum.dll,因为它起的作用是承上启下,一方面它要实现做DTO (Data Transfer
Object,数据转换对象), 另一方向也是真真正正的核心功能区(如积分,在线,用户,发贴,短消息,广告,公
告等)。所以如果要进行二次开发的话,了解它里面的代码越多越好。当然这一层我们是不建议开发人员做太大修
改的,因为我们为了方便调用和新功能的开发,做了很多的封装,就目前而言,完全可以在不修改代码的前提下实
现用户(注册,登陆,获取相关信息)整合和一些简单的二次开发。
详见官访说明: http://nt.discuz.net/showtopic-36265.html ,
文档:http://nt.discuz.net/download/doc/dnt_2_userapidoc.zip,
如下图:
需要大家投入更多注意力的应该是前台的aspx.cs页面。因为如果进行二次开发,以此为切入点是一个“投入少见效
快”的捷径。而如何做这方面的整合大家可以看一下官方文档。当然如果大家认为有必要,我也会在今后的文章中侧重聊
一聊这方面的话题。
另一个discuz.entity.dll其实只是一个实体类项目,当然它是非常干净的那种。甚至大部分构造函数都是以系统默
认方式提供的。因为只是用于DTO这样的数据对象绑定封装,这样做的好处相信大家也会猜出来。除了代码生成方便,便于
分布式系统下进行传输(只有对象数据),当然还有最重要的就是让开发人员不用过多关注数据对象上的操作(也就是逻辑)
。当然有人会说这样做会导致模型没有了逻辑,甚至连最基本的操作(CRUD:Create(创建)、Read(读取)、Update(更新)
和Delete(删除))都被拿走了。凭心而论(虽然我是个充血模型的fans,大家对域模型和UML感兴趣,可以看一下我的
ICONIX系列文章),但开发的现实告诉我,如果用充血模型开发,最糟的情况可能会是为了什么样的逻辑应该放在域对象
中,什么样的逻辑应该放在Business Logic中,以及对象之间的关系(如继承,包含等)和位置摆放(在那个名空间和DLL
中)就要争论半天,对于我们这个产品而言实在是得不尝失(时间紧,任务重呀)。
而我们这样架构的好处是:
1.各层单向依赖,结构清楚,易于实现和维护(可以看一下各项目相互的关系)
2.设计简单易行(走群众路线,降低开发门槛),底层架构相对稳定
......
相应的,我们产品“贫血模型”下的架构,如下图所示:
通过采用以数据(表)结构为导向(discuz.forum中的类基本上是按数据库中的表名进行命名)。这样即使是新来的
同事也能够很快理解并进行开发。因为架构很简单,只要将操作某个表的函数方法放在相应的类中即可。虽然将来可能会
导致类里的方法数量增加过快,业务逻辑趋于复杂,但完全可以通过重构(Refactor)方式来解决这个问题。
相信看到这里,很多人又要开始评头论足了! 请相信,我并不想将大家再次拖到几个月前那场关于域模型和OO的“讨论
泥潭”中去,因为讨论到最后也不可能说出个所以然了。我也不想再用本文去“煽风点火”,因为这并不是我写本文的最终
目的。因为关于“贫血”还是“充血”一直都是一个让人头痛的问题(就像现实和理想的差距)。
聊了一些题外话,下面言归正传,再看几个园子里朋友反映的问题.
请大家看一下Discuz.Data项目下的DbProvider文件夹里的IDataProvider(discuz.data.IDataProvider)接口,
这个接口本身定义了将近900个接口函数,而这些接口函数分别在Discuz.Data.SqlServer, Discuz.Data.MySql,
Discuz.Data.Access这几个项目的DataProvider类中加以实现,其中基本上都是SQL语句和存储过程调用(SQLSERVER)。
这些函数是针对整个产品与数据库进行数据访问才实现的。当然如果以后需要支持别的类型数据库时,还会加入如Discuz.
Data.(数据库名称)(如:Oracle等)这样的项目。而总体上这样设计的好处就是将SQL语句统一进行管理,便于维护和扩
展。同时也使访问数据库的写法趋于规范,避免SQL语句像“野草”一样在产品中“四处横行”:)
当然有些朋友认为,将所有接口函数放在一个接口类中加以声明显得过于臃肿。其实这个问题之前我也想到过,而我的
意见是将这个函数按功能(论坛,空间,相册,聚合等)分别定义形如:
2 {
3 .//空间数据访问操作函数
4 }
5
6 interface IForumDataProvider
7 {
8 .//论坛数据访问操作函数
9 }
10
11 interface IAlbumDataProvider
12 {
13 .//相册数据访问操作函数
14 }
15
16 .
17
18
而最后的IDataProvider的定义就会是:
2 {
3 //除空间,论坛,相册,聚合以外的所有函数方法
4 }
这样做的好处应该就是便于开发小组成员对各自的“开发边界”对一个共性的认识,便于分工和并行开发。
但因为作用没有那么明显,所以在开源之前一直没有这样划分,我也只是私下与小组成员聊过这个问题,并未向上面进
行反映。
另外一个我之前考虑的问题就是如果“第三方”来进行二次开发,可能会出现许多新的数据操作方法,而已有的功能是
不能满足的。而通过在IDataProvider 中添加相应方法又会使将来的更新升级(因为 3.0可能会有变化)变得非常麻烦。
所以目前我的一个想法就是添加第三方订制接口,形如:
2 {
3 // 在此加入第三方数据访问操作接口函数,用户只要定义并实现这些函数即可。
4 }
5
而IDataProvider 中相应添加一个字段,形如:
2 {
3 public IThirdParty {set;get;} //第三方数据访问接口实例
4 }
5
这样就可以把第三方新加的功能函数与系统本身的数据访问操作方法解耦,同时也提供了统一的接口调用方式,形如:
2
当然这些只是我私下的设想,我会在适当的时候将这个意见反映给开发小组。如果大家有什么别的建议或意见不妨在回
复中交流一下。
另外要说的一个项目就是 discuz.common了,当然这个项目的活最杂也最底层,相当于“打零工”。里面基本上都是整
个产品所要使用的基础类。当然还有一部分是对.net框架自身函数的“再度封装”,目的就是为了优化代码结构,减少“因
不了解.net函数”而造成的使用上的错误等等。
如discuz.common项目下的Xml文件夹里XmlDocumentExtender.cs,XmlVisitor.cs类就是这样的东东。
同时我还继承了 Generic.List
常用的访问操作。如:计数,累加等。
2 {
3 ///
4 /// 是否已运行
5 ///
6 bool HasDone { get; }
7
8 ///
9 /// 访问指定的对象
10 ///
11 void Visit(T obj);
12 }
13
14 ///
15 /// 累加数访问类
16 ///
17 public sealed class SumVisitor : IDiscuzVisitor
18 {
19
20 private int sum;
21
22 ///
23 /// 构造函数
24 ///
25 public SumVisitor()
26 { }
27
28 ///
29 /// 访问指定的对象
30 ///
31 public void Visit(int obj)
32 {
33 sum += obj;
34 }
35
36 ///
37 /// 是否已运行
38 ///
39 public bool HasDone
40 {
41 get
42 {
43 return false;
44 }
45 }
46
47 ///
48 /// 返加累加数
49 ///
50 public int Sum
51 {
52 get
53 {
54 return sum;
55 }
56 }
57 }
58
59
而在相应的泛型类中调用如下(摘自DiscuzList.cs):
2 ///
3 /// 接受指定的访问方式(访问者模式)
4 ///
5 ///
6 public void Accept(IDiscuzVisitor visitor)
7 {
8 if (visitor == null)
9 {
10 throw new ArgumentNullException("访问器为空");
11 }
12
13
14 System.Collections.Generic.List.Enumerator enumerator = this.GetEnumerator();
15
16 while (enumerator.MoveNext())
17 {
18 visitor.Visit(enumerator.Current);
19
20 if (visitor.HasDone)
21 {
22 return;
23 }
24 }
25 }
26
27
28
29
目前除了discuz.web.dll, discuz.space.dll, discuz.web.ui.dll之外,我们基本上了解了大多数dll文件的作用。
而这几个.dll的主要任务就是负责前台显示和后台设置的。如果大家觉得有必要的话,我会在后面的文章中做一下介绍。而有关
数据库(字段)的解释,我想在介绍 discuz.entity.dll时顺便作一下说明,请关心这个话题的朋友留意下周的文章。