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