鉴于前一阵子所写的关于Discuz!NT文章在园子中有些朋友存在疑惑这里先做一下声明:
这些关于Discuz!NT的文章不是要帮助大家把这个项目中所有的程序逻辑都解释一遭 (我
相信大多数朋友也不希望这么做) 而是希望能给大家提供一把“钥匙”,帮助大家从项目结构
和程序架构上先对这个产品有一个初步认识,想信只要有过一定开发项目经验的朋友应该从中
很快找到突破口,来挖掘出更多对大家有用的东西。当然如果大家认为我写做的方式有什么问
题,不妨直接回复,我会根据情况尽力修正的:)
当然这个项目还很不完善(从我个人角度来讲)。所以就更有必然在这里与大家进行交流,
我从不认为将更多更优秀的思想集中到这个项目中是什么不好的事情:)只要大家提出的合理的
意见,我就会向官方反应。必定开源项目本身就应该有着很好的“人缘”。当然大家可以尽情
的批评挑刺,因为很少有什么产品一出来就是优秀的,什么都需要千锤百炼。更合况一个刚发
展一年多的婴儿(discuz!nt)。我相信只要大家有足够的爱心和耐心伴随这个婴儿一起成长,
并帮助和关注它,就能最终见证开源这种方式在中国的发展轨迹(话说大了)。
好了,不扯了。开始今天的话题吧!
首先把这个聚合项目的架构图放出来,以便在下面的代码解释阶段详加说明:

设计背景:早在RC1之前聚合功能还比较弱化时,系统结构比较简单,只用了一个website
类就聚集了大部分的功能调用。但在快速开发完成之后陆续又加入了不少新功能,导致类的名
称(website) 与所聚合提供的功能已不完全应用相符 (代码已过度膨胀) ,所以重构的任务已
变得非常紧迫了。
但用什么方式,因为系统聚合时是按内容类型来聚合功能页面并决定显示方式的。而这里
的内容类型在大概可分为(论坛主题,相册,图片,空间文章(及最新回复)等)。为了尽量
简化系统设计时的复杂度,这里只按内容所属的大类(论坛,空间,相册等)来进行简单的初
步规划,这就产生出来上面图片所说的类AggregationData,SpaceAggregationData,
AlbumAggregationData.cs,ForumAggregationData.cs
看来这里完全可以将它们看为是四个子系统了,同时也可以直接将相应的前台显示控制逻
辑与这里面的相关子系统相联,但这就会少了一层封装在里面,另外这四个子系统类 (这里暂
且这么说吧之间)如果交互协为也需要有这么一个层以避免直接子系统之间的直接函数调用。
所以这里使用了facade来简单的封装这一层。
也就是如下的代码段了:
1 AggregationFacade.cs
2
3 private static AggregationData __baseAggregationData;
4
5 private static ForumAggregationData __forumAggregationData;
6
7 private static SpaceAggregationData __spaceAggregationData;
8
9 private static AlbumAggregationData __albumAggregationData;
10
11 private static PhotoAggregationData __photoAggregationData;
12
13 static AggregationFacade()
14 {
15 __baseAggregationData = new AggregationData();
16
17 __forumAggregationData = new ForumAggregationData();
18
19 __spaceAggregationData = new SpaceAggregationData();
20
21 __albumAggregationData = new AlbumAggregationData();
22
23 __photoAggregationData = new PhotoAggregationData();
24
25 //加载要通知的聚合数据对象,Attach函数将在下面内容中介绍
26 AggregationDataSubject.Attach(__baseAggregationData);
27
28 AggregationDataSubject.Attach(__forumAggregationData);
29
30 AggregationDataSubject.Attach(__spaceAggregationData);
31
32 AggregationDataSubject.Attach(__albumAggregationData);
33
34 AggregationDataSubject.Attach(__photoAggregationData);
35 }
36
而前端显示页面的数据对象获取将通过如下属性进行相关的操作和调用。
1 public static ForumAggregationData ForumAggregation
2 {
3 get
4 {
5 return __forumAggregationData;
6 }
7 }
8
目前已设计了数据读取时所使用的逻辑(相关的聚合数据类),但如果通过进程去更新相应的
已加载的数据 (因为在discuz!nt中的数据要考虑跨进程数据同步)。同时因为要更新的内容又过
于繁杂,所以要使用一种机制来解决这个问题。
这里我使用了Observer来尝试解决这个问题。
这里不妨将这个模式的图放在这里:

同时也有系统中所使用的相应设计如下,便于大家进行对比。

好的,而相关的模式实现代码如下(AggregationDataSubject类中):
1 #region 采用Observer模式清空当前进程中的聚合数据
2
3 private static ArrayList __aggregationDataArrayList = new ArrayList();
4
5
6 //调用在AggregationFacade类的静态构造函数中
7 public static void Attach(AggregationData __aggregationData)
8 {
9 __aggregationDataArrayList.Add(__aggregationData);
10 }
11
12 public static void Detach(AggregationData __aggregationData)
13 {
14 __aggregationDataArrayList.Remove(__aggregationData);
15 }
16
17 public static void NotifyClearDataBind()
18 {
19 foreach (AggregationData __aggregationData in __aggregationDataArrayList)
20 {
21 __aggregationData.ClearDataBind();
22 }
23 }
24
25 #endregion
26
而调用Attach的函数 (初始化要操作的对象数组) 在AggregationFacade的静态构造函数中。
上面已加了说明:)
另外在这个类中还使用了定时器来定时检查相关数据文件中的修改日期,如果为真则调用集合数
组中对象的ClearDataBind()方法以便让相关的数据对象为null,相关逻辑如下
1 //设置定时器时间为15秒
2 private static System.Timers.Timer aggregationConfigTimer = new System.Timers.Timer(15000);
3
4 //AggregationDataSubject类的静态构造函数
5 static AggregationDataSubject()
6 {
7 

8 //初始化定时器
9 aggregationConfigTimer.AutoReset = true;
10 aggregationConfigTimer.Enabled = true;
11 aggregationConfigTimer.Elapsed += new System.Timers.ElapsedEventHandler(Timer_Elapsed);
12 aggregationConfigTimer.Start();
13 }
14
15 private static void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
16 {
17 if (IsFileHadRewrite())
18 {
19 //重设文件修改时间,以便下次文件更新时进行逻辑判断
20 ReSetFileChangeTime();
21
22 //重新从数据文件中读取数据
23 AggregationData.ReadAggregationConfig();
24
25 //调用上面的相关函数
26 NotifyClearDataBind();
27 }
28 }
这样当后台修改任何聚合数据页面 (aggregation.config) 的数据后,都会使aggregation.config文件被更
新(文件修改时间会发生变化)。
这时前台页面调用数据时就会根据这个判断来决定是从数据文件或数据库中重新加载数据还是使用已
初始化的数据了。
这里为了说明只举了一个例子,其实这样的程序逻辑在这个项目文件中有不少.
1 private DataTable topNComment;
2 public DataTable GetSpaceTopCommentsFromFile()
3 {
4 //注意此处的判断
5 if (topNComment != null)
6 {
7 return topNComment;
8 }
9
10 //从文件中取
11 topNComment = aggregationDS.Tables["topncommentspace"];
12
13 

14 return topNComment;
15 }
为什么使用这样的方式呢。其实是出现进程并发时效率的考虑,因为之前的程序逻辑是当HTTP请求到来时
程序会去判断数据文件是否更新并决定是否从新加载最新的数据信息。但当我们在官方使用这种逻辑后却发现
当并发上来时(50人在线),就出现了对象引用为空的情况。而纠其原因就要频繁的磁盘IO访问导致程序运行
速度的下降。后来在一个偶然的条件下我想起了可以用定时器将数据进行按时加载定制,才使用这种方式。必
定现在数据访问与数据更新从偶合在一起变成了相互分离,互不影响。且在后来基本上就没再出现前面所说的
对象引用为空的问题。所以在这里就直接借签了这种方式。 (当然大家如果有什么更好的方式,不妨在这里聊
上一聊:)
这样这个项目中的主要架构已全部说明完了。而相关的调用数据库的逻辑大家只要 reflactor或等官方提
供源码下载一看便知.
题外话:
其实这里还有一种思想要与大家交流,那就是设计模式是否该在项目的详细设计阶段(类图)出现前后就该
确定下来?
说实话,以前我的软件设计理念是只要对行业有足够的了解和对用户需求把握的够精确就可以使用模式来
分解设计时出现的复杂度和降低类之间的耦合系数。必定有系统架构师这个职位在做这方面的工作(当然这个
职位还有其它重要的职责)。但极限编程中所说的过度工程(over-engineering)所描述的残酷现实又让这种
过早出现的设计“激情”荡然无存。必定“对完美的追求无法写出实用的代码,而实用是软件压倒一切的要素”
(摘自重构书中的“译序”) 。这就好比一上来就用一块好钢去打造一辆MINI轿车的原形 (尽管设计非常完美),
但当制造出来后却发现这辆车的车主竟然是姚明。
这种事在这里可以只是当成个笑话,但我相信如果发生在你的项目中你就笑不出声了。
所以我也想借助这篇文章来与大家探讨一下到底是在"系统详细设计(详细类图) 前后"时还是在"代码编写
后重构"发生时应用模式 (尽管模式是重构的目标,而重构是模式的起点和源泉)。我相信大家肯定会有许多不
同的观点要说。
另外本人也在思考是否存在什么“中庸之道”来调合这两种不同观点之间的冲突:)
posted on 2007-09-18 11:57
代震军 阅读(4903)
评论(36) 编辑 收藏 网摘 所属分类:
设计模式VS2005其它Discuz!NT
发表评论
@曾哲
谢谢,有空请您聊聊"题外话"那部分内容,说真话,我特想了解大家对这块内容的观点:)
关于题外话的一点想法。
个人感觉一些比较明显的模式(我们之前开发经验告诉我们这个地方需要使用模式的)在设计的时候就应该使用模式。不需要考虑现在的需求是否需要这个模式。就如同数据库设计我们需要遵循范式。一般情况都是在设计的时候为了这个那个理由违背了范式,之后只能给无尽的麻烦擦屁股了。
不确定或者不明显的地方,之前也没有相关经验的地方大可不必考虑太多。项目过程中或者项目完成后,肯定都存在review的。这个时候可以随需求的变化对代码进行重构么。
我们现在正在打造一个行业门户网站,论坛就用了Discuz!NT 2.0.9 RC2,这两天正在安排人测试。期待发布正式版和源码,楼主加油,Discuz!加油!
ls的,你的问题的答案是肯定的。 我想你大概想要看到(Free software,自由软件)
大家知道我为什么对这个不感兴趣吗?
因为他们多次约定了时间发布源代码。
我按他们的说的时间去了多次。每次都失望。
因此,我认为他们只是一种广告而已。
本来有的那点热情,也被这多次折腾掉了。
@有意思。
其实您的这种说法在官方站点早就有过来,我不想再多说什么,只是想告诉大家,既然公司已作了承诺就会兑现.对开源本身作任何猜测我想都是没什么意义的:)
@有意思
您咋没留下网址呢? 我去您blog看看。 看看到底怎么个意思..
两点:
你们这块的代码我没有看过, 但是凡是Observer模式能够实现的, 事件驱动都能够实现(甚至更好的实现), 而且对于.NET来说更原生一些, 我想你可以考虑一下, 设计模式可以活学活用, 变成很不相同的形态. 不过你们要考虑将来在Java系统上的实现, 我就不多说了 :)
第二点, 给出源代码不等于开源, 开源除了出, 还有进, 而且有其固定的模式/流程和管理. 你们没有考虑建立一个类似于SourceForge上那样, 但是管理更严格的形式吗? 不过协议是个问题.
完毕.
最后说一句我自己的偏见, 通知永远比询问合理(你的Observer模式已经体现), 类比更底层的原理, 就是中断永远比论询合理.
但你的内部的实现, 关于文件是否更新的问题, 实际上还是用计时器轮询, 在.NET里似乎有Dependency的相关的东东, 你可以看看. 当然微软的实现我没有仔细琢磨, 可能也是论询, 但如果它不是论询, 或日后升级, 你就得到一个很好的结果, 而且以后也省劲儿了.
另外一点, 如果用计时器反复询问, 那么其实你们原来的方法也是可以的, 比如一个消耗更小的办法, 每次来一个请求就i++, i能整除某数时就动作(不过万一请求较少, 更新就较慢), i过大就清零; 也可以记录Http请求的间隔时间; 当然,也可以监视CPU使用率,IO频率, 较小时就更新...
@怪怪
谢谢你的意见,我会认真考虑的,必定这个东西还只是一个初始模型,还存在修正和提升的余地:)
@怪怪
通知比轮询合理放在Web下有问题的,Web下的数据是无法Push到客户端的,所有的行为的起始点都是用户在浏览器上的动作,所以通知也只能到Logic这一个层,再上头的只能靠拉模式了。
static 的 observer , 骇人听闻. 还有对象图呢.
@STS
其实大可不必这样,本来模式就是要活学活用才会发挥其应有的威力
易虎互联 00p(www.easyhoo.cn)是在现有网络文化内涵的基础上,以全新的网络理念、先进的网络技术和企业管理模式建构的。本公司以多种网络经营为载体,致力于为广大用户提供:各行业各领域、“先进的 、易懂的 、实用的、 普及的”网络商务平台,是一家拥有多元化营销网络及专业服务网络的高科技公司。 2007年易虎互联深入分析互联网行业,对大量企业网站调研,针对当前企业网站的弊端,提出“营销型”网站概念,掀起广州新一轮的企业建网风暴,开创广州企业网站建设蓝海。我们的口号:“绝不做没有销售力的网站!”
易虎互联 (www.easyhoo.cn)是在现有网络文化内涵的基础上,以全新的网络理念、先进的网络技术和企业管理模式建构的。本公司以多种网络经营为载体,致力于为广大用户提供:各行业各领域、“先进的 、易懂的 、实用的、 普及的”网络商务平台,是一家拥有多元化营销网络及专业服务网络的高科技公司。 2007年易虎互联深入分析互联网行业,对大量企业网站调研,针对当前企业网站的弊端,提出“营销型”网站概念,掀起广州新一轮的企业建网风暴,开创广州企业网站建设蓝海。oop我们的口号:“绝不做没有销售力的网站!”
好东西,期待开发源码:)
最近正在使用.net版本搭建论坛,要是有源码就好了,有些bug就可以很快修正了
@flyingchen
原码应该在正式版推出后不久就会放出来,但具体时间不清楚,必定这个要公司高层决定。而我只是一个普通开发者而言。同时我是绝对支持彻底开源的,这点请大家放心,起码在这个立场上我是与大家坚定站在一起的:)
我看了你们的程序,其中确实有很多值得好好学习,深刻钻研的地方。
在这里甚至可以叫你一声老师!
您过奖了,必定大家都是在为自己的公司出谋划策,希望上级能够给予重视,还是相互学习吧,呵呵
刚弄.net,不知道那里可以下载到Discuz!nt2.0完整源码
"这就好比一上来就用一块好钢去打造一辆MINI轿车的原形 (尽管设计非常完美),但当制造出来后却发现这辆车的车主竟然是姚明" 楼主好有才!
严重支持
楼主强啊,想问一下,AggregationFacade这个类是在什么时候调用的?
@arsenal
在前台aspx.cs页面中进行调用,比如website.aspx.cs文件里
if (!Utils.FileExists(AggregationData.DataFilePath))
{
string content = "<?xml version=\"1.0\" standalone=\"yes\"?>\r\n";
content += "<remove>\r\n<table1 xpath=\"example\" removedatetime=\""+DateTime.Now.ToShortDateString()+"\" />\r\n</remove>";
using (FileStream fs = new FileStream(AggregationData.DataFilePath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite))
{
Byte[] info = System.Text.Encoding.UTF8.GetBytes(content);
fs.Write(info, 0, info.Length);
fs.Close();
}
}
这都什么代码 整个项目29处有类似的代码 却没有见到有共用的文件读写操作类?是我没找吗 还是写了又没用。。。
设置定时器时间为15秒 为什么定时器是15秒呢 而且还是硬编码在项目中
如果我的用户很少 只需要1小时更新一次呢
还有一个看似很巧妙的NotifyClearDataBind 为什么我三个聚合非要用相同的通知时间呢 感觉只是简单的对设计模式的生搬硬套
关于设计只要解决问题 又何必在乎是什么模式 什么时候重构
@游客
首先,下面这段代码用于当相应的config目录下没有初始化创建想应的缓存数据文件时会调用,这段代码在聚合项目中只有一处,绝没有你所说的29段那么多,还是建议你好好看一下代码后再说:
if (!Utils.FileExists(AggregationData.DataFilePath))
.....
其次,定时器的15秒是我们评估站长所做的后台数据修改与前端用户访问时的一般操作同步时间,这个值写死,是为了不想让用户设置而导致一些数据更新显示时的不同步问题。另外你所说的15秒更新其实是你没看完代码就主观臆测罢了,请看一下这段代码的执行条件:
if (IsFileHadRewrite())
{
ReSetFileChangeTime();
AggregationData.ReadAggregationConfig();
NotifyClearDataBind();
}
其中的IsFileHadRewrite是当文件的更新时间发生变化时都清空想应的对象(NotifyClearDataBind)。所以只有后台管理员修改了文件中的数据之后,才会最终清空前台的相应static对象数据。而你所说的三个聚合要用同一个时间是因为这三个聚合中的数据都被存于同一个文件中,具体参照:
discuz.web\config\aggregation.config 这个文件即可。
另外这块使用设计模式是为了让程序结构清晰,让这几个聚合类之间解耦,绝不是故弄玄虚。相信了解observer模式的朋友会非常容易理解我的设计意图。
想问一下这个聚合项目的主要功能是什么,看到有些项目像Discuz.Forum项目在reference中没有对其进行引用,而有些项目中像Discuz.Album中有对其的引用,想问一下具体这个项目抛出来的数据是什么?
而Discuz.Web这个项目有对Discuz.Forum和Discuz.Aggregation同时引用,想知道一下web页面中的数据消费的是这两个项目中的哪些数据,如果都有的话,那么个项目分别负责什么数据呢,而为什么要这么分,有什么原因吗,本人是初学者,还请老师解释!谢谢!