浅谈三层结构的原理与用意.docx
- 文档编号:13102754
- 上传时间:2023-06-11
- 格式:DOCX
- 页数:31
- 大小:182.67KB
浅谈三层结构的原理与用意.docx
《浅谈三层结构的原理与用意.docx》由会员分享,可在线阅读,更多相关《浅谈三层结构的原理与用意.docx(31页珍藏版)》请在冰点文库上搜索。
浅谈三层结构的原理与用意
浅谈“三层结构”的原理与用意
对于有经验的Web应用程序开发人员来说,“三层结构”一词应该不会感到陌生。
其实“三层结构”的开发模式不仅仅可以应用于Web应用程序,在其他应用领域也是可以发挥其巨大作用的。
而本文主旨是阐明三层结构的原理与用意,并说明Bincess的三层结构的特点。
“三层结构”指的是什么?
“三层结构”一词中的“三层”是指:
“外观层”、“中间层”、“数据库层”。
其中:
☐外观层:
位于最外层,直接呈现在用户面前。
用于显示数据,并为用户提供一种交互式的界面。
☐中间层:
负责处理用户输入的信息,或者是将这些信息发送给数据库层进行保存,或者是调用数据库层中的函数再次读出这些数据。
☐数据库层:
仅实现对数据的保存和读取操作。
为什么需要“三层结构”
在一个软件系统中,如果不分以层次,那么在将来的升级维护中会遇到很大的麻烦。
就像一个ASP.NET网页访问数据库一样。
例如在ASP.NET后台程序文件aspx.cs中,使用OleDbConnection和OleDbCommand来处理Access后台数据库。
而当数据库服务器从Access2000升迁到SQLServer2000的时候,我们就必须修改原来的OleDbConnection为新的SqlConnection,OleDbCommand为新的SqlCommand来适应新的数据库服务器。
但问题是对于一个大型的商业网站,要进行数据库操作的并不只有一两个页面。
访问数据库的代码会散落各个页面中,就像夜空中的星星一样。
这样的维护,难度可想而知。
有一个比较好的解决办法,那就是将访问数据库的代码全部都放在一个cs文件里,这样数据库服务器一旦变换,那么只需要集中修改一个cs文件就可以了。
namespaceBincess//ListBoard.aspx.cs文件
{
publicclassListBoard
{
privatevoidBoardDataBind()
{
OleDbConnectiondbConn=newOleDbConnection();
OleDbCommanddbCmd=newOleDbCommand();
...
}
}
}
namespaceBincess//ListTopic.aspx.cs文件
{
publicclassListTopic
{
privatevoidTopicDataBind()
{
OleDbConnectiondbConn=newOleDbConnection();
OleDbCommanddbCmd=newOleDbCommand();
...
}
}
}
将原来的访问数据库的代码全部都放在DBTask.cs程序文件中,这样只要修改这一个文件就可以适应新的数据库
namespaceBincess//DBTask.cs
{
publicclassDBTask
{
publicvoidBoardDataBind()
{
OleDbConnectiondbConn=newOleDbConnection();
OleDbCommanddbCmd=newOleDbCommand();
...
}
publicvoidTopicDataBind()
{
OleDbConnectiondbConn=newOleDbConnection();
OleDbCommanddbCmd=newOleDbCommand();
...
}
}
}
namespaceBincess//ListBoard.aspx.cs文件
{
publicclassListBoard
{
privatevoidBoardDataBind()
{
(newDBTask()).BoardDataBind();
}
}
}
namespaceBincess//ListTopic.aspx.cs文件
{
publicclassListTopic
{
privatevoidTopicDataBind()
{
(newDBTask()).TopicDataBind();
}
}
}
当然这是一个简单的“门面模式”的应用,恐怕也是“三层结构”的最原始模型…
如果数据库访问代码太多,令DBTask.cs文件过大的话,可以将函数功能分组,存储到其它文件里。
怎样才算是一个符合“三层结构”的Web应用程序?
在一个ASP.NETWeb应用程序解决方案中,并不是说有aspx文件、有dll文件、还有数据库,就是“三层结构”的Web应用程序,这样的说法是不对的。
也并不是说没有对数据库进行操作,即没有“数据库层”,就不是“三层结构”的。
其实三层结构是功能实现上的三层:
☐外观层,用于显示,并为用户提供交互式操作的可能…
☐中间层,服务于外观层并调用数据库层的函数。
☐数据库层,实现数据库的存储和读出。
存储目标不一定是数据库服务器,也可以是文本文档或XML文档。
在微软的ASP.NET示范实例Duwamish7中,外观层被放置在Web项目中,中间层是放置在BusinessFacade项目中,而数据库层则是放置在DataAccess项目中。
而微软的另一个ASP.NET示范实例PetShop中,外观层被放置在Web项目中,中间层是放置在BLL项目中,而数据库层则是放置在SQLServerDAL和OracleDAL两个项目中。
在我的彬月论坛中,外观层是被放置在Web项目中,中间层是被放置在InterService项目中,而数据库层是被放置在AccessTask项目中。
显然PetShop要比Duwamish7复杂的多!
如果先不讨论这些,那么现在的问题就是:
既然三层结构已经被分派到各自的项目中,那么剩下来的项目是做什么的呢?
例如PetShop中的Model、IDAL、DALFactory这三个项目,再例如Duwamish7中的Common项目,还有就是在我的论坛中的Classes、DBTask、DALFactory三个项目。
它们是做什么用的呢?
我想下面的文字会慢慢让你明白的。
从Nokia的手机生产线说起
一个“三层结构”的Web应用程序,就象是Nokia公司的手机生产线。
☐Web层就像是公司的经理,他负责洞察市场趋势,决策产品的生产。
并根据市场筹策下一步计划。
☐InterService就像是公司的管理员,他主要负责管理下层员工,传达上级布置的生产任务给员工,并将生产结果反馈给上级Web。
☐AccessTask就是公司里的工人,他们主要是负责手机产品的生产装配工作,并将生产结果反馈给上级InterService。
他们并不需要知道产品将销往何处,也不用关心产品销量。
只要能完成任务,就可以拿到报酬。
命令方向是自上而下的,而结果反馈方向则是自下而上的。
根据这个图例来简要的描述彬月论坛中的留言板显示功能,那么代码应该是:
--//首先是ListLeaveWord.aspx这个文件//-->
Repeaterid=″leaveWordRepeater″Runat=″SERVER″> <%#DataItem.Eval(Container.DataItem,″Content″)%>
Repeater>
//其后台文件是由位于Web层的ListLeaveWord.aspx.cs文件发出读取留言板信息的任务
//任务命令交由LeaveWordService类对象的List方法去完成。
usingSystem;
usingSystem.Data;
usingBincess.InterService;
namespaceWeb
{
publicclassListLeaveWord:
System.Web.UI.Page
{
//将留言视图绑定到该重复器上
protectedSystem.Web.UI.WebControls.RepeaterleaveWordRepeater;
//其他代码...
//--------------------------------------------------------------------
//绑定留言视图
//--------------------------------------------------------------------
privatevoidLeaveWordDataBind()
{
DataSetds=newDataSet();
(newLeaveWordService()).List(ds);
leaveWordRepeater.DataSource=ds.Tables[″LeaveWord″].DefaultView;
leaveWordRepeater.DataBind();
}
}
//位于中间层的LeaveWordService.cs调用下一层数据库层的方法来填充数据集
//
usingSystem;
usingSystem.Data;
usingBincess.AccessTask;
namespaceInterService
{
publicclassLeaveWordService
{
publicvoidList(DataSetds)
{
(newLeaveWordDBTask()).List(ds)
}
}
}
//位于数据库层的LeaveWordDBTask.cs,他真正完成将数据读出并将其填充到数据集中
//
usingSystem;
usingSystem.Data;
usingSystem.Data.OleDb;
namespaceAccessTask
{
publicclassLeaveWordDBTask
{
publicvoidList(DataSetds)
{
stringcmdText=″SELECT*FROM[LeaveWord]″;
OleDbConnectiondbConn=newOleDbConnection(″...″);
OleDbDataAdapterdbAdp=newOleDbDataAdapter(cmdText,dbConn);
dbAdp.Fill(ds,″LeaveWord″);
}
}
}
这样便完成了对留言板的访问和显示,箭头所指的方向就是命令的方向。
虽然这符合“三层结构”开发模式的思想,但是这却存在着重大的漏洞,或者说是重大缺陷!
为什么会这么说呢?
因为从中间层返回的结果是不安全的!
而造成中间层返回结果不安全的原因是从数据库层返回的结果并不确切!
这会造成外观层过于脆弱,这并不是一个“强”三层结构。
还是用代码来说明。
假如,LeaveWordDBTask.cs文件中的List方法实现是这样的:
//位于数据库层的LeaveWordDBTask.cs文件
namespaceAccessTask
{
publicclassLeaveWordDBTask
{
publicvoidList(DataSetds)
{
stringcmdText=″SELECT*FROM[RegUser]″;//注意这里,访问的不是LeaveWord数据表
OleDbConnectiondbConn=newOleDbConnection(″...″);
OleDbDataAdapterdbAdp=newOleDbDataAdapter(cmdText,dbConn);
dbAdp.Fill(ds,″LeaveWord″);//但是也填充了DataSet
}
}
}
那么回逆到文件LeaveWordService.cs的List函数,再回逆到ListLeaveWord.aspx.cs文件的LeaveWordDataBind函数。
把数据绑定到重复器上,而在显示的时候,会提示:
找不到Content字段!
Repeaterid=″leaveWordRepeater″Runat=″SERVER″> <%#DataItem.Eval(Container.DataItem,″Content″)%><--在这里会出现错误提示
Repeater>
出现这样的结果并不奇怪,因为数据库层访问的是RegUser数据表,而RegUser数据表中并没有定义Content字段。
外观层因此变得很脆弱,这也使得页面设计师和数据库编程人员产生了不应有的交涉。
仅仅为了达到程序可运行目的,数据库编程人员就必须小心翼翼的写每句代码。
这就像是NoKia公司的经理发布生产命令后,得到的返回结果却是生产线上的员工生产装配了好几台电视?
!
这当然不是经理们想要的结果。
但为什么会有这样结果呢?
因为经理们在发布生产命令时,忘记说明产品的规格和特征了。
经理一声令下:
“生产!
”——(newLeaveWordService()).List(DataSetds)
但是却没有对产品的规格特征作详细说明?
例如手机的型号、外观等等。
这里的ds就相当于所要生产的产品集合,但却没有作细部说明…
那么怎样才能避免这样荒唐的结果出现呢?
经理在发布生产命令之前,应该规定产品的规格特征!
经理一声令下:
“生产3310型号的手机产品!
”——(newLeaveWordService()).List(LeaveWordDataSetds)
这里的ds就相当于所要生产的产品集合,而且它有详细的规格说明!
//LeaveWordDataSet.cs文件
//
usingSystem;
usingSystem.Data;
namespaceCommon
{
publicclassLeaveWordDataSet:
DataSet
{
publicLeaveWordDataSet():
base()
{
DataTabletable=newDataTable(″LeaveWord″);
table.Columns.Add(″Content″,typeof(System.String));
this.Tables.Add(table);
}
}
}
那么原来的示意图应该也发生一些变化:
相应的代码也要变化:
--//首先是ListLeaveWord.aspx这个文件//-->
Repeaterid=″leaveWordRepeater″Runat=″SERVER″> <%#DataItem.Eval(Container.DataItem,″Content″)%>
Repeater>
//其后台文件是由位于Web层的ListLeaveWord.aspx.cs文件发出读取留言板信息的任务
//任务命令交由LeaveWordService类对象的List方法去完成。
usingSystem;
usingSystem.Data;
usingBincess.InterService;
usingBincess.Common;//在彬月论坛中所使用的是Bincess.Classes名称空间
namespaceWeb
{
publicclassListLeaveWord:
System.Web.UI.Page
{
//将留言视图绑定到该重复器上
protectedSystem.Web.UI.WebControls.RepeaterleaveWordRepeater;
//--------------------------------------------------------------------
//绑定留言视图,为了简明扼要省略了其他代码...
//--------------------------------------------------------------------
privatevoidLeaveWordDataBind()
{
LeaveWordDataSetds=newLeaveWordDataSet();
(newLeaveWordService()).List(ds);
leaveWordRepeater.DataSource=ds.Tables[″LeaveWord″].DefaultView;
leaveWordRepeater.DataBind();
}
}
//位于中间层的LeaveWordService.cs调用下一层数据库层的方法来填充数据集
//
usingSystem;
usingSystem.Data;
usingBincess.AccessTask;
usingBincess.Common;//在彬月论坛中所使用的是Bincess.Classes名称空间
namespaceInterService
{
publicclassLeaveWordService
{
publicvoidList(LeaveWordDataSetds)
{
(newLeaveWordDBTask()).List(ds)
}
}
}
//位于数据库层的LeaveWordDBTask.cs,他真正完成将数据读出并将其填充到数据集中
//
usingSystem;
usingSystem.Data;
usingSystem.Data.OleDb;
usingBincess.Common;//在彬月论坛中所使用的是Bincess.Classes名称空间
namespaceAccessTask
{
publicclassLeaveWordDBTask
{
publicvoidList(LeaveWordDataSetds)
{
stringcmdText=″SELECT*FROM[LeaveWord]″;
OleDbConnectiondbConn=newOleDbConnection(″...″);
OleDbDataAdapterdbAdp=newOleDbDataAdapter(cmdText,dbConn);
dbAdp.Fill(ds,″LeaveWord″);
}
}
}
这样,即便是将LeaveWordTask.List方法修改成访问RegUser数据表的代码,也依然不会影响到外观层。
再执行期间系统会抛出异常,而这个异常信息肯定是再数据库层。
再有,因为位于外观层的重复器控件绑定的是LeaveWordDataSet类对象,而LeaveWordDataSet类中就一定有Content字段的定义。
当然,这同时也达到了规范数据库层返回结果的目的。
这便是为什么在Duwamish7中会出现Common项目的原因。
不知道你现在看明白了么?
而Bincess的做法是和PetShop一样,是通过类定义来达到同样的目的!
PetShop是通过Modal项目,而Bincess是通过Classes项目。
为了举例和易于理解,我在上面的例子中使用了Bincess.Common名称空间,但实际的Bincess论坛,却不是这样实现的。
你可以到Classes目录中去查看代码。
//LeaveWordTask.cs文件
//
using...
namespaceBincess.AccessTask
{
///
///LeaveWordTask留言板数据库任务实现类
///
publicclassLeaveWordTask:
ILeaveWordTask
{
类LeaveWordTask默认构造器
///
///列表留言板内容
///
///
publicLeaveWord[]List()
{
OleDbConnectiondbConn=newOleDbConnection(″...″);
OleDbCommanddbCmd=newOleDbCommand(cmdText,dbConn);
//其他代码...
}
}
}
//LeaveWord.cs留言板类定义
//
usingSystem;
namespaceBincess.Classes.Message
{
///
///LeaveWord浏览板类
///
publicclassLeaveWord:
BaseMsg...
}
而用于显示留言板的控件,你可以参见PageCtrl/MessageView/LeaveWordView.ascx文件
再谈手机的装配方法
在上一步中,经理说明了产品型号和规格。
InterService层的LeaveWordService调用了AccessTask层中的LeaveWordTask类List函数来获取留言数据。
在Bincess论坛中使用Classes层LeaveWord类来规范化Web层与AccessTask层的返回结果。
那么在InterService和AccessTask之间有没有需要规范的呢?
先别那么多的想法,先假设一个很现实的例子。
假如,Bincess论坛的数据库要从Access2000升迁到MSSqlServer2000,那么只要集中修改AccessTask项目中的所有文件以时应新的数据库服务器即可。
不过,可不可以让论坛同时支持Access又支持MSSqlServer2000?
通过一个开关,当开关指向Access一端时,Bincess就可以运行在Access数据库平台上,而如果开关指向MSSqlServer2000时,Bincess就运行在MSSqlServer数据库服务器上?
这个办法很好!
但是怎么实现呢?
先在解决方案中新建一个项目就叫作:
SqlServerTask。
然后还有建立一个项目DALFactory充当开关。
这个开关项目中仅有一个DBTaskDriver.cs文件,就用它来控制Bincess到底运行载那个数据库平台上。
//新建的SqlServerTask项目,并添加一个LeaveWordTask.cs文件
//
usingSystem;
using
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 浅谈 三层 结构 原理 用意