浅谈“三层结构”原理与用意 4
为什么需要“三层结构”?——数据库升迁、应用程序变化所带来的问题
留言板正式投入使用!但没过多久,我准备把这个留言板程序的数据库升迁到microsoft sql server 2000服务器上去!除了要把数据导入到sql server 2000中,还得修改相应的.aspx.cs程序文件。也就是说需要把调用oledbconnection的地方修改成sqlconnection,还要把调用oledbadapter的地方,修改成sqladapter。虽然这并不是一件很困难的事情,因为整个站点非常小,仅仅只有两个程序文件,所以修改起来并不费劲。但是,如果对于一个大型的商业网站,访问数据库的页面有很多很多,如果以此方法一个页面一个页面地进行修改,那么费时又费力!只是修改了一下数据库,却可能要修改上千张网页。一动百动,这也许就是程序的一种不灵活性……
再假如,我想给留言板加一个限制:
n 每天上午09时之后到11时之前可以留言,下午则是13时之后到17时之前可以留言
n 如果当天留言个数小于 40,则可以继续留言
那么就需要把相应的代码,添加到postlword.aspx.cs程序文件中。但是过了一段时间,我又希望去除这个限制,那么还要修改postlword.aspx.cs文件。但是,对于一个大型的商业网站,类似于这样的限制,或者称为“商业规则”,复杂又繁琐。而且这些规则很容易随着商家的意志为转移。如果这些规则限制被分散到各个页面中,那么规则一旦变化,就要修改很多的页面!只是修改了一下规则限制,却又可能要修改上千张网页。一动百动,这也许又是程序的一种不灵活性……
最后,留言板使用过一段时间之后,出于某种目的,我希望把它修改成可以在本地运行的windows程序,而放弃原来的web型式。那么对于这个留言板,可以说是“灭顶之灾”。所有代码都要重新写……当然这个例子比较极端,在现实中,这样的情况还是很少会发生的——
为什么需要“三层结构”?——初探,就从数据库的升迁开始
一个站点中,访问数据库的程序代码散落在各个页面中,就像夜空中的星星一样繁多。这样一动百动的维护,难度可想而知。最难以忍受的是,对这种维护工作的投入,是没有任何价值的……
有一个比较好的解决办法,那就是将访问数据库的代码全部都放在一个程序文件里。这样,数据库平台一旦发生变化,那么只需要集中修改这一个文件就可以了。我想有点开发经验的人,都会想到这一步的。这种“以不变应万变”的做法其实是简单的“门面模式”的应用。如果把一个网站比喻成一家大饭店,那么“门面模式”中的“门面”,就像是饭店的服务生,而一个网站的浏览者,就像是一个来宾。来宾只需要发送命令给服务生,然后服务生就会按照命令办事。至于服务生经历了多少辛苦才把事情办成?那个并不是来宾感兴趣的事情,来宾们只要求服务生尽快把自己交待事情办完。我们就把listlword.aspx.cs程序就看成是一个来宾发出的命令,而把新加入的lwordtask.cs程序看成是一个饭店服务生,那么来宾发出的命令就是:
“给我读出留言板数据库中的数据,填充到dataset数据集中并显示出来!”
而服务生接到命令后,就会依照执行。而postlword.aspx.cs程序,让服务生做的是:
“把我的留言内容写入到数据库中!”
而服务生接到命令后,就会依照执行。这就是tracelword2!可以在codepackage/tracelword2目录中找到——
把所有的有关数据访问的代码都放到lwordtask.cs文件里,lwordtask.cs程序文件如下:
#001 using system;
#002 using system.data;
#003 using system.data.oledb; // 需要操作 access 数据库
#004 using system.web;
#005
#006 namespace tracelword2
#007 {
#008 /// <summary>
#009 /// lwordtask 数据库任务类
#010 /// </summary>
#011 public class lwordtask
#012 {
#013 // 数据库连接字符串
#014 private const string db_conn=@"provider=microsoft.jet.oledb.4.0;
data source=c:\dbfs\tracelworddb.mdb";
#015
#016 /// <summary>
#017 /// 读取数据库表 lword,并填充 dataset 数据集
#018 /// </summary>
#019 /// <param name="ds">填充目标数据集</param>
#020 /// <param name="tablename">表名称</param>
#021 /// <returns>记录行数</returns>
#022 public int listlword(dataset ds, string tablename)
#023 {
#024 string cmdtext="select * from [lword] order by [lwordid] desc";
#025
#026 oledbconnection dbconn=new oledbconnection(db_conn);
#027 oledbdataadapter dbadp=new oledbdataadapter(cmdtext, dbconn);
#028
#029 int count=dbadp.fill(ds, tablename);
#030
#031 return count;
#032 }
#033
#034 /// <summary>
#035 /// 发送留言信息到数据库
#036 /// </summary>
#037 /// <param name="textcontent">留言内容</param>
#038 public void postlword(string textcontent)
#039 {
#040 // 留言内容不能为空
#041 if(textcontent==null || textcontent=="")
#042 throw new exception("留言内容为空");
#043
#044 string cmdtext="insert into [lword]([textcontent]) values(@textcontent)";
#045
#046 oledbconnection dbconn=new oledbconnection(db_conn);
#047 oledbcommand dbcmd=new oledbcommand(cmdtext, dbconn);
#048
#049 // 设置留言内容
#050 dbcmd.parameters.add(new oledbparameter("@textcontent", oledbtype.longvarwchar));
#051 dbcmd.parameters["@textcontent"].value=textcontent;
#052
#053 try
#054 {
#055 dbconn.open();
#056 dbcmd.executenonquery();
#057 }
#058 catch
#059 {
#060 throw;
#061 }
#062 finally
#063 {
#064 dbconn.close();
#065 }
#066 }
#067 }
#068 }
如果将数据库从access 2000修改为sql server 2000,那么只需要修改lwordtask.cs这一个文件。如果lwordtask.cs文件太大,也可以把它切割成几个文件或“类”。如果被切割成的“类”还是很多,也可以把这些访问数据库的类放到一个新建的“项目”里。当然,原来的listlword.aspx.cs文件应该作以修改,lword_databind函数被修改成:
...
#046 private void lword_databind()
#047 {
#048 dataset ds=new dataset();
#049 (new lwordtask()).listlword(ds, @"lwordtable");
#050
#051 m_lwordlistctrl.datasource=ds.tables[@"lwordtable"].defaultview;
#052 m_lwordlistctrl.databind();
#053 }
...
原来的postlword.aspx.cs文件也应作以修改,post_serverclick函数被修改成:
...
#048 private void post_serverclick(object sender, eventargs e)
#049 {
#050 // 获取留言内容
#051 string textcontent=this.m_txtcontent.value;
#052
#053 (new lwordtask()).postlword(textcontent);
#054
#055 // 跳转到留言显示页面
#056 response.redirect("listlword.aspx", true);
#057 }
...
从前面的程序段中可以看出,listlword.aspx.cs和postlword.aspx.cs这两个文件已经找不到和数据库相关的代码了。只看到一些和lwordtask类有关系的代码,这就符合了“设计模式”中的一种重要原则:“迪米特法则”。“迪米特法则”主要是说:让一个“类”与尽量少的其它的类发生关系。在tracelword1中,listlword.aspx.cs这个类和oledbconnection及oledbdataadapter都发生了关系,所以它破坏了“迪米特法则”。利用一个“中间人”是“迪米特法则”解决问题的办法,这也是“门面模式”必须遵循的原则。下面就引出这个lwordtask门面类的示意图: