Discuz!NT 聚合功能页面程序架构(重构到Facade与Observer模式)
鉴于前一阵子所写的关于Discuz!NT文章在园子中有些朋友存在疑惑这里先做一下声明:
这些关于Discuz!NT的文章不是要帮助大家把这个项目中所有的程序逻辑都解释一遭 (我
相信大多数朋友也不希望这么做) 而是希望能给大家提供一把“钥匙”,帮助大家从项目结构
和程序架构上先对这个产品有一个初步认识,想信只要有过一定开发项目经验的朋友应该从中
很快找到突破口,来挖掘出更多对大家有用的东西。当然如果大家认为我写做的方式有什么问
题,不妨直接回复,我会根据情况尽力修正的:)
当然这个项目还很不完善(从我个人角度来讲)。所以就更有必然在这里与大家进行交流,
我从不认为将更多更优秀的思想集中到这个项目中是什么不好的事情:)只要大家提出的合理的
意见,我就会向官方反应。必定开源项目本身就应该有着很好的“人缘”。当然大家可以尽情
的批评挑刺,因为很少有什么产品一出来就是优秀的,什么都需要千锤百炼。更合况一个刚发
展一年多的婴儿(discuz!nt)。我相信只要大家有足够的爱心和耐心伴随这个婴儿一起成长,
并帮助和关注它,就能最终见证开源这种方式在中国的发展轨迹(话说大了)。
好了,不扯了。开始今天的话题吧!
首先把这个聚合项目的架构图放出来,以便在下面的代码解释阶段详加说明:
![](/images/cnblogs_com/daizhj/project_class_draw.JPG)
设计背景:早在RC1之前聚合功能还比较弱化时,系统结构比较简单,只用了一个website
类就聚集了大部分的功能调用。但在快速开发完成之后陆续又加入了不少新功能,导致类的名
称(website) 与所聚合提供的功能已不完全应用相符 (代码已过度膨胀) ,所以重构的任务已
变得非常紧迫了。
但用什么方式,因为系统聚合时是按内容类型来聚合功能页面并决定显示方式的。而这里
的内容类型在大概可分为(论坛主题,相册,图片,空间文章(及最新回复)等)。为了尽量
简化系统设计时的复杂度,这里只按内容所属的大类(论坛,空间,相册等)来进行简单的初
步规划,这就产生出来上面图片所说的类AggregationData,SpaceAggregationData,
AlbumAggregationData.cs,ForumAggregationData.cs
看来这里完全可以将它们看为是四个子系统了,同时也可以直接将相应的前台显示控制逻
辑与这里面的相关子系统相联,但这就会少了一层封装在里面,另外这四个子系统类 (这里暂
且这么说吧之间)如果交互协为也需要有这么一个层以避免直接子系统之间的直接函数调用。
所以这里使用了facade来简单的封装这一层。
也就是如下的代码段了:
目前已设计了数据读取时所使用的逻辑(相关的聚合数据类),但如果通过进程去更新相应的
已加载的数据 (因为在discuz!nt中的数据要考虑跨进程数据同步)。同时因为要更新的内容又过
于繁杂,所以要使用一种机制来解决这个问题。
这里我使用了Observer来尝试解决这个问题。
这里不妨将这个模式的图放在这里:
![](/images/cnblogs_com/daizhj/observers.jpg)
同时也有系统中所使用的相应设计如下,便于大家进行对比。
![](/images/cnblogs_com/daizhj/discuz_aggregation_observer.JPG)
好的,而相关的模式实现代码如下(AggregationDataSubject类中):
上面已加了说明:)
另外在这个类中还使用了定时器来定时检查相关数据文件中的修改日期,如果为真则调用集合数
组中对象的ClearDataBind()方法以便让相关的数据对象为null,相关逻辑如下
新(文件修改时间会发生变化)。
这时前台页面调用数据时就会根据这个判断来决定是从数据文件或数据库中重新加载数据还是使用已
初始化的数据了。
这里为了说明只举了一个例子,其实这样的程序逻辑在这个项目文件中有不少.
为什么使用这样的方式呢。其实是出现进程并发时效率的考虑,因为之前的程序逻辑是当HTTP请求到来时
程序会去判断数据文件是否更新并决定是否从新加载最新的数据信息。但当我们在官方使用这种逻辑后却发现
当并发上来时(50人在线),就出现了对象引用为空的情况。而纠其原因就要频繁的磁盘IO访问导致程序运行
速度的下降。后来在一个偶然的条件下我想起了可以用定时器将数据进行按时加载定制,才使用这种方式。必
定现在数据访问与数据更新从偶合在一起变成了相互分离,互不影响。且在后来基本上就没再出现前面所说的
对象引用为空的问题。所以在这里就直接借签了这种方式。 (当然大家如果有什么更好的方式,不妨在这里聊
上一聊:)
这样这个项目中的主要架构已全部说明完了。而相关的调用数据库的逻辑大家只要 reflactor或等官方提
供源码下载一看便知.
题外话:
其实这里还有一种思想要与大家交流,那就是设计模式是否该在项目的详细设计阶段(类图)出现前后就该
确定下来?
说实话,以前我的软件设计理念是只要对行业有足够的了解和对用户需求把握的够精确就可以使用模式来
分解设计时出现的复杂度和降低类之间的耦合系数。必定有系统架构师这个职位在做这方面的工作(当然这个
职位还有其它重要的职责)。但极限编程中所说的过度工程(over-engineering)所描述的残酷现实又让这种
过早出现的设计“激情”荡然无存。必定“对完美的追求无法写出实用的代码,而实用是软件压倒一切的要素”
(摘自重构书中的“译序”) 。这就好比一上来就用一块好钢去打造一辆MINI轿车的原形 (尽管设计非常完美),
但当制造出来后却发现这辆车的车主竟然是姚明。
这种事在这里可以只是当成个笑话,但我相信如果发生在你的项目中你就笑不出声了。
所以我也想借助这篇文章来与大家探讨一下到底是在"系统详细设计(详细类图) 前后"时还是在"代码编写
后重构"发生时应用模式 (尽管模式是重构的目标,而重构是模式的起点和源泉)。我相信大家肯定会有许多不
同的观点要说。
另外本人也在思考是否存在什么“中庸之道”来调合这两种不同观点之间的冲突:)
这些关于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
而前端显示页面的数据对象获取将通过如下属性进行相关的操作和调用。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
2 {
3 get
4 {
5 return __forumAggregationData;
6 }
7 }
8
目前已设计了数据读取时所使用的逻辑(相关的聚合数据类),但如果通过进程去更新相应的
已加载的数据 (因为在discuz!nt中的数据要考虑跨进程数据同步)。同时因为要更新的内容又过
于繁杂,所以要使用一种机制来解决这个问题。
这里我使用了Observer来尝试解决这个问题。
这里不妨将这个模式的图放在这里:
![](/images/cnblogs_com/daizhj/observers.jpg)
同时也有系统中所使用的相应设计如下,便于大家进行对比。
好的,而相关的模式实现代码如下(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的静态构造函数中。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
上面已加了说明:)
另外在这个类中还使用了定时器来定时检查相关数据文件中的修改日期,如果为真则调用集合数
组中对象的ClearDataBind()方法以便让相关的数据对象为null,相关逻辑如下
1 //设置定时器时间为15秒
2 private static System.Timers.Timer aggregationConfigTimer = new System.Timers.Timer(15000);
3
4 //AggregationDataSubject类的静态构造函数
5 static AggregationDataSubject()
6 {
7![](https://www.cnblogs.com/Images/dot.gif)
![](https://www.cnblogs.com/Images/dot.gif)
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文件被更2 private static System.Timers.Timer aggregationConfigTimer = new System.Timers.Timer(15000);
3
4 //AggregationDataSubject类的静态构造函数
5 static AggregationDataSubject()
6 {
7
![](https://www.cnblogs.com/Images/dot.gif)
![](https://www.cnblogs.com/Images/dot.gif)
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 }
新(文件修改时间会发生变化)。
这时前台页面调用数据时就会根据这个判断来决定是从数据文件或数据库中重新加载数据还是使用已
初始化的数据了。
这里为了说明只举了一个例子,其实这样的程序逻辑在这个项目文件中有不少.
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![](https://www.cnblogs.com/Images/dot.gif)
![](https://www.cnblogs.com/Images/dot.gif)
14 return topNComment;
15 }
2 public DataTable GetSpaceTopCommentsFromFile()
3 {
4 //注意此处的判断
5 if (topNComment != null)
6 {
7 return topNComment;
8 }
9
10 //从文件中取
11 topNComment = aggregationDS.Tables["topncommentspace"];
12
13
![](https://www.cnblogs.com/Images/dot.gif)
![](https://www.cnblogs.com/Images/dot.gif)
14 return topNComment;
15 }
为什么使用这样的方式呢。其实是出现进程并发时效率的考虑,因为之前的程序逻辑是当HTTP请求到来时
程序会去判断数据文件是否更新并决定是否从新加载最新的数据信息。但当我们在官方使用这种逻辑后却发现
当并发上来时(50人在线),就出现了对象引用为空的情况。而纠其原因就要频繁的磁盘IO访问导致程序运行
速度的下降。后来在一个偶然的条件下我想起了可以用定时器将数据进行按时加载定制,才使用这种方式。必
定现在数据访问与数据更新从偶合在一起变成了相互分离,互不影响。且在后来基本上就没再出现前面所说的
对象引用为空的问题。所以在这里就直接借签了这种方式。 (当然大家如果有什么更好的方式,不妨在这里聊
上一聊:)
这样这个项目中的主要架构已全部说明完了。而相关的调用数据库的逻辑大家只要 reflactor或等官方提
供源码下载一看便知.
题外话:
其实这里还有一种思想要与大家交流,那就是设计模式是否该在项目的详细设计阶段(类图)出现前后就该
确定下来?
说实话,以前我的软件设计理念是只要对行业有足够的了解和对用户需求把握的够精确就可以使用模式来
分解设计时出现的复杂度和降低类之间的耦合系数。必定有系统架构师这个职位在做这方面的工作(当然这个
职位还有其它重要的职责)。但极限编程中所说的过度工程(over-engineering)所描述的残酷现实又让这种
过早出现的设计“激情”荡然无存。必定“对完美的追求无法写出实用的代码,而实用是软件压倒一切的要素”
(摘自重构书中的“译序”) 。这就好比一上来就用一块好钢去打造一辆MINI轿车的原形 (尽管设计非常完美),
但当制造出来后却发现这辆车的车主竟然是姚明。
这种事在这里可以只是当成个笑话,但我相信如果发生在你的项目中你就笑不出声了。
所以我也想借助这篇文章来与大家探讨一下到底是在"系统详细设计(详细类图) 前后"时还是在"代码编写
后重构"发生时应用模式 (尽管模式是重构的目标,而重构是模式的起点和源泉)。我相信大家肯定会有许多不
同的观点要说。
另外本人也在思考是否存在什么“中庸之道”来调合这两种不同观点之间的冲突:)
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· .NET Core内存结构体系(Windows环境)底层原理浅谈
· C# 深度学习:对抗生成网络(GAN)训练头像生成模型
· .NET 适配 HarmonyOS 进展
· 用 DeepSeek 给对象做个网站,她一定感动坏了
· DeepSeek+PageAssist实现本地大模型联网
· 手把手教你更优雅的享受 DeepSeek
· Java轻量级代码工程
· 从 14 秒到 1 秒:MySQL DDL 性能优化实战