为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

浅谈“三层结构”原理与用意

2011-07-14 44页 doc 1MB 11阅读

用户头像

is_351797

暂无简介

举报
浅谈“三层结构”原理与用意浅谈“三层结构”的原理与用意 浅谈“三层结构”原理与用意 序 在刚刚步入“多层结构”Web应用程序开发的时候,我阅读过几篇关于“asp.net三层结构开发”的文章。但其多半都是对PetShop3.0和Duwamish7的局部剖析或者是学习笔记。对“三层结构”通体分析的学术文章几乎没有。 2005年2月11日,Bincess BBS彬月论坛开始试运行。不久之后,我写了一篇题目为《浅谈“三层结构”原理与用意》的文章。旧版文章以彬月论坛程序中的部分代码举例,通过全局视角阐述了什么是“三层结构”的开发模式?为什么要这样做?怎样做?……...
浅谈“三层结构”原理与用意
浅谈“三层结构”的原理与用意 浅谈“三层结构”原理与用意 序 在刚刚步入“多层结构”Web应用程序开发的时候,我阅读过几篇关于“asp.net三层结构开发”的文章。但其多半都是对PetShop3.0和Duwamish7的局部剖析或者是学习笔记。对“三层结构”通体分析的学术文章几乎没有。 2005年2月11日,Bincess BBS彬月论坛开始试运行。不久之后,我写了一篇题目为《浅谈“三层结构”原理与用意》的文章。旧版文章以彬月论坛程序中的部分代码举例,通过全局视角阐述了什么是“三层结构”的开发模式?为什么要这样做?怎样做?……而在这篇文章的新作中,配合这篇文章我写了7个程序实例(TraceLWord1~TraceLWord7留言板)以帮助读者理解“三层结构”应用程序。这些程序示例可以在随带的CodePackage目录中找到——   对于那些有丰富经验的Web应用程序开发人员,他们认为文章写的通俗易懂,很值得一读。可是对于asp.net初学者,特别是没有任何开发经验的人,文章阅读起来就感到非常困难,不知文章所云。甚至有些读者对“三层结构”的认识更模糊了……   关于“多层结构”开发模式,存在这样一种争议:一部分学者认为“多层结构”与“面向对象的程序设计思想”有着非常紧密的联系。而另外一部分学者却认为二者之间并无直接联系。写作这篇文章并不是要终结这种争议,其行文目的是希望读者能够明白:在使用asp.net进行Web应用程序开发时,实现“多层结构”开发模式的方法、原理及用意。要顺利的阅读这篇文章,希望读者能对“面向对象的程序设计思想”有一定深度的认识,最好能懂一些“设计模式”的知识。如果你并不了解前面这些,那么这篇文章可能并不适合你现在阅读。不过,无论这篇文章面对的读者是谁,我都会尽量将文章写好。我希望这篇文章能成为学习“三层结构”设计思想的经典文章! “三层结构”是什么?   “三层结构”一词中的“三层”是指:“现层”、“中间业务层”、“数据访问层”。其中: ​ 表 现 层:位于最外层(最上层),离用户最近。用于显示数据和接收用户输入的数据,为用户提供一种交互式操作的界面。 ​ 中间业务层:负责处理用户输入的信息,或者是将这些信息发送给数据访问层进行保存,或者是调用数据访问层中的函数再次读出这些数据。中间业务层也可以包括一些对“商业逻辑”描述代码在里面。 ​ 数据访问层:仅实现对数据的保存和读取操作。数据访问,可以访问数据库系统、二进制文件、文本文档或是XML文档。   对依赖方向的研究将是本文的重点,数值返回方向基本上是没有变化的。 为什么需要 “三层结构”?——通常的设计方式   在一个大型的Web应用程序中,如果不分以层次,那么在将来的升级维护中会遇到很大的麻烦。但在这篇文章里我只想以一个简单的留言板程序为示例,说明通常设计方式的不足—— 功能说明: ListLWord.aspx(后台程序文件 ListLWord.aspx.cs)列表显示数据库中的每条留言。 PostLWord.aspx(后台程序文件 PostLWord.aspx.cs)发送留言到数据库。 更完整的示例代码,可以到CodePackage/TraceLWord1目录中找到。数据库中,仅含有一张数据表,其结构如下: 字段名称 数据类型 默认值 备注说明 [LWordID] INT NOT NULL IDENTITY(1, 1) 留言记录编号 [TextContent] NText N’’ 留言 [PostTime] DateTime GetDate() 留言发送时间,默认值为当前时间 ListLWord.aspx 页面文件(列表显示留言) #001 <%@ Page language="c#" Codebehind="ListLWord.aspx.cs" AutoEventWireup="false" Inherits="TraceLWord1.ListLWord" %> #002 #003 #004 #005 #006 ListLWord #007 #008 #009 #010 #011 #012 #013 #014
#015 #016 发送新留言 #017 #018 #019 #020
#021 <%# DataBinder.Eval(Container.DataItem, "PostTime") %> #022 <%# DataBinder.Eval(Container.DataItem, "TextContent") %> #023
#024
#025
#026 #027
#028 #029 #030 以最普通的设计方式制作留言板,效率很高。 这些代码可以在Visual Studio.NET 2003开发环境的设计视图中快速建立。 ListLWord.aspx 后台程序文件 ListLWord.aspx.cs #001 using System; #002 using System.Collections; #003 using System.ComponentModel; #004 using System.Data; #005 using System.Data.OleDb; // 需要操作 Access 数据库 #006 using System.Drawing; #007 using System.Web; #008 using System.Web.SessionState; #009 using System.Web.UI; #010 using System.Web.UI.WebControls; #011 using System.Web.UI.HtmlControls; #012 #013 namespace TraceLWord1 #014 { #015 /// #016 /// ListLWord 列表留言板信息 #017 /// #018 public class ListLWord : System.Web.UI.Page #019 { #020 // 留言列表控件 #021 protected System.Web.UI.WebControls.DataList m_lwordListCtrl; #022 #023 /// #024 /// ListLWord.aspx 页面加载函数 #025 /// #026 private void Page_Load(object sender, System.EventArgs e) #027 { #028 LWord_DataBind(); #029 } #030 #031 #region Web 窗体设计器生成的代码 #032 override protected void OnInit(EventArgs e) #033 { #034 InitializeComponent(); #035 base.OnInit(e); #036 } #037 #038 private void InitializeComponent() #039 { #040 this.Load+=new System.EventHandler(this.Page_Load); #041 } #042 #endregion #043 #044 /// #045 /// 绑定留言信息列表 #046 /// #047 private void LWord_DataBind() #048 { #049 string mdbConn=@"PROVIDER=Microsoft.Jet.OLEDB.4.0; DATA Source=C:\DbFs\TraceLWordDb.mdb"; #050 string cmdText=@"SELECT * FROM [LWord] ORDER BY [LWordID] DESC"; #051 #052 OleDbConnection dbConn=new OleDbConnection(mdbConn); #053 OleDbDataAdapter dbAdp=new OleDbDataAdapter(cmdText, dbConn); #054 #055 DataSet ds=new DataSet(); #056 dbAdp.Fill(ds, @"LWordTable"); #057 #058 m_lwordListCtrl.DataSource=ds.Tables[@"LWordTable"].DefaultView; #059 m_lwordListCtrl.DataBind(); #060 } #061 } #062 } PostLWord.aspx页面文件(发送留言到数据库) #001 <%@ Page language="c#" Codebehind="PostLWord.aspx.cs" AutoEventWireup="false" Inherits="TraceLWord1.PostLWord" %> #002 #003 #004 #005 #006 PostLWord #007 #008 #009 #010 #011 #012 #013 #014
#015 #016 #017 #018 #019
#020 #021 #022 PostLWord.aspx后台程序文件PostLWord.aspx.cs #001 using System; #002 using System.Collections; #003 using System.ComponentModel; #004 using System.Data; #005 using System.Data.OleDb; // 需要操作 Access 数据库 #006 using System.Drawing; #007 using System.Web; #008 using System.Web.SessionState; #009 using System.Web.UI; #010 using System.Web.UI.WebControls; #011 using System.Web.UI.HtmlControls; #012 #013 namespace TraceLWord1 #014 { #015 /// #016 /// PostLWord 发送留言到数据库 #017 /// #018 public class PostLWord : System.Web.UI.Page #019 { #020 // 留言内容编辑框 #021 protected System.Web.UI.HtmlControls.HtmlTextArea m_txtContent; #022 // 提交按钮 #023 protected System.Web.UI.HtmlControls.HtmlInputButton m_btnPost; #024 #025 /// #026 /// PostLWord.aspx 页面加载函数 #027 /// #028 private void Page_Load(object sender, System.EventArgs e) #029 { #030 } #031 #032 #region Web 窗体设计器生成的代码 #033 override protected void OnInit(EventArgs e) #034 { #035 InitializeComponent(); #036 base.OnInit(e); #037 } #038 #039 private void InitializeComponent() #040 { #041 this.Load+=new System.EventHandler(this.Page_Load); #042 this.m_btnPost.ServerClick+=new EventHandler(Post_ServerClick); #043 } #044 #endregion #046 /// #047 /// 发送留言信息到数据库 #048 /// #049 private void Post_ServerClick(object sender, EventArgs e) #050 { #051 // 获取留言内容 #052 string textContent=this.m_txtContent.Value; #053 #054 // 留言内容不能为空 #055 if(textContent=="") #056 throw new Exception("留言内容为空"); #057 #058 string mdbConn=@"PROVIDER=Microsoft.Jet.OLEDB.4.0; DATA Source=C:\DbFs\TraceLWordDb.mdb"; #059 string cmdText="INSERT INTO [LWord]([TextContent]) VALUES(@TextContent)"; #060 #061 OleDbConnection dbConn=new OleDbConnection(mdbConn); #062 OleDbCommand dbCmd=new OleDbCommand(cmdText, dbConn); #063 #064 // 设置留言内容 #065 dbCmd.Parameters.Add(new OleDbParameter("@TextContent", OleDbType.LongVarWChar)); #066 dbCmd.Parameters["@TextContent"].Value=textContent; #067 #068 try #069 { #070 dbConn.Open(); #071 dbCmd.ExecuteNonQuery(); #072 } #073 catch #074 { #075 throw; #076 } #077 finally #078 { #079 dbConn.Close(); #080 } #081 #082 // 跳转到留言显示页面 #083 Response.Redirect("ListLWord.aspx", true); #084 } #085 } #086 } 仅仅通过两个页面,就完成了一个基于Access数据库的留言功能。 程序并不算复杂,非常简单清楚。但是随后你会意识到其存在着不灵活性! 为什么需要“三层结构”?——数据库升迁、应用程序变化所带来的问题 留言板正式投入使用!但没过多久,我准备把这个留言板程序的数据库升迁到Microsoft SQL Server 2000服务器上去!除了要把数据导入到SQL Server 2000中,还得修改相应的.aspx.cs程序文件。也就是说需要把调用OleDbConnection的地方修改成SqlConnection,还要把调用OleDbAdapter的地方,修改成SqlAdapter。虽然这并不是一件很困难的事情,因为整个站点非常小,仅仅只有两个程序文件,所以修改起来并不费劲。但是,如果对于一个大型的商业网站,访问数据库的页面有很多很多,如果以此方法一个页面一个页面地进行修改,那么费时又费力!只是修改了一下数据库,却可能要修改上千张网页。一动百动,这也许就是程序的一种不灵活性…… 再假如,我想给留言板加一个限制: ​ 每天上午09时之后到11时之前可以留言,下午则是13时之后到17时之前可以留言 ​ 如果当天留言个数小于 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 /// #009 /// LWordTask 数据库任务类 #010 /// #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 /// #017 /// 读取数据库表 LWord,并填充 DataSet 数据集 #018 /// #019 /// 填充目标数据集 #020 /// 表名称 #021 /// 记录行数 #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 /// #035 /// 发送留言信息到数据库 #036 /// #037 /// 留言内容 #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门面类的示意图: ListLWord.aspx.cs和PostLWord.aspx.cs两个文件对数据库的访问,全部委托LWordTask类这个“中间人”来办理。利用“门面模式”,将页面类和数据库类进行隔离。这样就作到了页面类不依赖于数据库的效果。以一段比较简单的代码来描述这三个程序的关系: public class ListLWord { private void LWord_DataBind() { (new LWordTask()).ListLWord( ... ); } } public class PostLWord { private void Post_ServerClick(object sender, EventArgs e) { (new LWordTask()).PostLWord( ... ); } } public class LWordTask { public DataSet ListLWord(DataSet ds)... public void PostLWord(string textContent)... } 应用中间业务层,实现“三层结构” 前面这种分离数据访问代码的形式,可以说是一种“三层结构”的简化形式。因为它没有“中间业务层”也可以称呼它为“二层结构”。一个真正的“三层”程序,是要有“中间业务层”的,而它的作用是连接“外观层”和“数据访问层”。换句话说:“外观层”的任务先委托给“中间业务层”来办理,然后“中间业务层”再去委托“数据访问层”来办理…… 那么为什么要应用“中间业务层”呢?“中间业务层”的用途有很多,例如:验证用户输入数据、缓存从数据库中读取的数据等等……但是,“中间业务层”的实际目的是将“数据访问层”的最基础的存储逻辑组合起来,形成一种业务规则。例如:“在一个购物网站中有这样的一个规则:在该网站第一次购物的用户,系统为其自动注册”。这样的业务逻辑放在中间层最合适: 在“数据访问层”中,最好不要出现任何“业务逻辑”!也就是说,要保证“数据访问层”的中的函数功能的原子性!即最小性和不可再分。“数据访问层”只管负责存储或读取数据就可以了。   在新TraceLWord3中,应用了“企业级模板项目”。把原来的LWordTask.cs,并放置到一个单一的项目里,项目名称为:AccessTask。解决中又新建了一个名称为:InterService的项目,该项目中包含一个LWordService.cs程序文件,它便是“中间业务层”程序。为了不重复命名,TraceLWord3的网站被放置到了WebUI项目中。更完整的代码,可以在CodePackage/TraceLWord3目录中找到—— 这些类的关系,也可以表示为如下的示意图: LWordService.cs程序源码: #001 using System; #002 using System.Data; #003 #004 using TraceLWord3.AccessTask; // 引用数据访问层 #005 #006 namespace TraceLWord3.InterService #007 { #008 /// #009 /// LWordService 留言板服务类 #010 /// #011 public class LWordService #012 { #013 /// #014 /// 读取数据库表 LWord,并填充 DataSet 数据集 #015 /// #016 /// 填充目标数据集 #017 /// 表名称 #018 /// 记录行数 #019 public int ListLWord(DataSet ds, string tableName) #020 { #021 return (new LWordTask()).ListLWord(ds, tableName); #022 } #023 #024 /// #025 /// 发送留言信息到数据库 #026 /// #027 /// 留言内容 #028 public void PostLWord(string content) #029 { #030 (new LWordTask()).PostLWord(content); #031 } #032 } #033 } 从LWordService.cs程序文件的行#021和行#030可以看出,“中间业务层”并没有实现什么业务逻辑,只是简单的调用了“数据访问层”的类方法……这样做是为了让读者更直观的看明白“三层结构”应用程序的调用顺序,看清楚它的全貌。加入了“中间业务层”,那么原来的ListLWord.aspx.cs文件应该作以修改: ... #012 using TraceLWord3.InterService; // 引用中间服务层 ... #045 /// #046 /// 绑定留言信息列表 #047 /// #048 private void LWord_DataBind() #049 { #050 DataSet ds=new DataSet(); #051 (new LWordService()).ListLWord(ds, @"LWordTable"); #052 #053 m_lwordListCtrl.DataSource=ds.Tables[@"LWordTable"].DefaultView; #054 m_lwordListCtrl.DataBind(); #055 } ... 原来的PostLWord.aspx.cs文件也应作以修改: ... #012 using TraceLWord3.InterService; // 引用中间服务层 ... #047 /// #048 /// 发送留言到数据库 #049 /// #050 private void Post_ServerClick(object sender, EventArgs e) #051 { #052 // 获取留言内容 #053 string textContent=this.m_txtContent.Value; #054 #055 (new LWordService()).PostLWord(textContent); #056 #057 // 跳转到留言显示页面 #058 Response.Redirect("ListLWord.aspx", true); #059 } ... 到目前为止,TraceLWord3程序已经是一个简单的“三层结构”的应用程序,以一段比较简单的代码来描述四个程序的关系: namespace TraceLWord3.WebLWord { public class ListLWord { private void LWord_DataBind() { (new LWordService()).ListLWord( ... ); } } public class PostLWord { private void Post_ServerClick(object sender, EventArgs e) { (new LWordService()).PostLWord( ... ); } } } namespace TraceLWord3.InterService { public class LWordTask { public DataSet ListLWord(DataSet ds, string tableName) { return (new LWordTask()).ListLWord(ds, tableName); } public void PostLWord(string content) { (new LWordTask()).PostLWord(content); } } } namespace TraceLWord3.AccessTask { public class LWordTask { public DataSet ListLWord(DataSet ds)... public void PostLWord(string content)... } } 用户在访问TraceLWord3的ListLWord.aspx页面时序图: 当一个用户访问TraceLWord5的ListLWord.aspx页面的时候,会触发该页面后台程序中的Page_Load函数。而在该函数中调用了LWord_DataBind函数来获取留言板信息。由图中可以看到出,LWord_DataBind在被调用的期间,会建立一个新的LWordService类对象,并调用这个对象的ListLWord函数。在LWordService.ListLWord函数被调用的期间,会建立一个新的LWordTask类对象,并调用这个对象的ListLWord来获取留言板信息的。PostLWord.aspx页面时序图,和上面这个差不多。就是这样,经过一层又一层的调用,来获取返回结果或是保存数据。 注意:从时序图中可以看出,当子程序模块未执行结束时,主程序模块只能处于等待状态。这说明将应用程序划分层次,会带来其执行速度上的一些损失…… 对“三层结构”的深入理解——怎样才算是一个符合“三层结构”的Web应用程序? 在一个ASP.NET Web应用程序解决方案中,并不是说有aspx文件、有dll文件、还有数据库,就是“三层结构”的Web应用程序,这样的说法是不对的。也并不是说没有对数据库进行操作,就不是“三层结构”的。其实“三层结构”是功能实现上的三层。例如,在微软的ASP.NET示范实例“Duwamish7”中,“表现层”被放置在“Web”项目中,“中间业务层”是放置在“BusinessFacade”项目中,“数据访问层”则是放置在“DataAccess”项目中……而在微软的另一个ASP.NET示范实例“PetShop3.0”中,“表现层”被放置在“Web”项目中,“中间业务层”是放置在“BLL”项目中,而“数据访问层”则是放置在“SQLServerDAL”和“OracleDAL”两个项目中。在Bincess.CN彬月论坛中,“表现层”是被放置在“WebForum”项目中,“中间业务(服务)层”是被放置在“InterService”项目中,而“数据访问层”是被放置在“SqlServerTask”项目中。   如果只以分层的设计角度看,Duwamish7要比PetShop3.0复杂一些!而如果较为全面的比较二者,PetShop3.0则显得比较复杂。但我们先不讨论这些,对PetShop3.0和Duwamish7的研究,并不是本文的重点。现在的问题就是:既然“三层结构”已经被分派到各自的项目中,那么剩下来的项目是做什么的呢?例如PetShop3.0中的“Model”、“IDAL”、“DALFactory”这三个项目,再例如Duwamish7中的“Common”项目,还有就是在Bincess.CN彬月论坛中的“Classes”、“DbTask”、这两个项目。它们究竟是做什么用的呢? 对“三层结构”的深入理解——从一家小餐馆说起   一个“三层结构”的Web应用程序,就好象是一家小餐馆。 ​ 表 现 层,所有的.aspx页面就好像是这家餐馆的菜谱。 ​ 中间业务层,就像是餐馆的服务生。 ​ 数据访问层,就像是餐馆的大厨师傅。 ​ 而我们这些网站浏览者,就是去餐馆吃饭的吃客了…… 我们去一家餐馆吃饭,首先得看他们的菜谱,然后唤来服务生,告诉他我们想要吃的菜肴。服务生记下来以后,便会马上去大厨师傅要烹制这些菜。大厨师傅收到通知后,马上起火烧菜。过了不久,服务生便把一道一道香喷喷的、热气腾腾的美味端到我们的桌位上—— 而我们访问一个基于asp.net技术的网站的时候,首先打开的是一个aspx页面。这个aspx页面的后台程序会去调用中间业务层的相应函数来获取结果。中间业务层又会去调用数据访问层的相应函数来获取结果。在一个用户访问TraceLWord3打开ListLWord.aspx页面查看留言的时候,其后台程序ListLWord.aspx.cs会去调用位于中间业务层LWordService的ListLWord(DataSet ds)函数。然后这个函数又会去调用位于数据访问层AccessTask的ListLWord(DataSet ds)函数。最后把结果显示出来…… 对比一下示意图: 从示意图看,这两个过程是否非常相似呢? 不同的地方只是在于,去餐馆吃饭,需要吃客自己唤来服务生。而访问一个asp.net网站,菜单可以代替吃客唤来服务生。在最后的返回结果上,把结果返回给aspx页面,也就是等于把结果返回给浏览者了。 高度的“面向对象思想”的体现——封装 在我们去餐馆吃饭的这个过程中,像我这样在餐馆中的吃客,最关心的是什么呢?当然是:餐馆的饭菜是不是好吃,是不是很卫生?价格是不是公道?……而餐馆中的服务生会关心什么呢?应该是:要随时注意响应每位顾客的吩咐,要记住顾客在哪个桌位上?还要把顾客点的菜记在本子上……餐馆的大厨师傅会关心什么呢?应该是:一道菜肴的做法是什么?怎么提高烧菜的效率?研究新菜式……大厨师傅,烧好菜肴之后,只管把菜交给服务生就完事了。至于服务生把菜送到哪个桌位上去了?是哪个顾客吃了他做的菜,大厨师傅才不管咧——服务生只要记得把我点的菜肴端来,就成了。至于这菜是怎么烹饪的?顾客干麻要点这道菜?他才不管咧——而我,只要知道这菜味道不错,价格公道,干净卫生,其他的我才不管咧—— 这里面不正是高度的体现了“面向对象思想”的“封装”原则吗? 无论大厨师傅在什么时候研究出新的菜式,都不会耽误我现在吃饭。就算服务生忘记我的桌位号是多少了,也不可能因此让大厨师傅忘记菜肴的做法?在我去餐馆吃饭的这个过程中,我、餐馆服务生、大厨师傅,是封装程度极高的三个个体。当其中的一个个体内部发生变化的时候,并不会波及到其他个体。这便是面向对象封装特性的一个益处! 土豆炖牛肉盖饭与实体   在我工作过的第一家公司楼下,有一家成都风味的小餐馆,每天中午我都和几个同事一起去那家小餐馆吃饭。公司附近只有这么一家餐馆,不过那里的饭菜还算不错。我最喜欢那里的“土豆炖牛肉盖饭”,也很喜欢那里的“鸡蛋汤”,那种美味至今难忘……所谓“盖饭”,又称是“盖浇饭”,就是把烹饪好的菜肴直接遮盖在铺在盘子里的米饭上。例如“土豆炖牛肉盖饭”,就是把一锅热气腾腾的“土豆炖牛肉”遮盖在米饭上—— 当我和同事再次来到这家餐馆吃饭,让我们想象以下这样的情形: 情形一: 我对服务生道:给我一份好吃的! 服务生道:什么好吃的? 我答道:一份好吃的—— 三番几次…… 我对服务生大怒道:好吃的,好吃的,你难道不明白吗?!—— 这样的情况是没有可能发生的!因为我没有明确地说出来我到底要吃什么?所以服务生也没办法为我服务…… 问题后果:我可能被送往附近医院的精神科…… 情形二: 我对服务生道:给我一份土豆炖牛肉盖饭! 服务生对大厨师傅道:做一份宫爆鸡丁—— 这样的情况是没有可能发生的!因为我非常明确地说出来我要吃土豆炖牛肉盖饭!但是服务生却给我端上了一盘宫爆鸡丁?! 问题后果:我会投诉这个服务生的…… 情形三: 我对服务生道:给我一份土豆炖牛肉盖饭! 服务生对大厨师傅道:做一份土豆炖牛肉盖饭—— 大厨师傅道:宫爆鸡丁做好了…… 这样的情况是没有可能发生的!因为我非常明确地说出来我要吃土豆炖牛肉盖饭!服务生也很明确地要求大厨师傅做一份土豆炖牛肉盖饭。但是厨师却烹制了一盘宫爆鸡丁?! 问题后果:我会投诉这家餐馆的…… 情形四: 我对服务生道:给一份土豆炖牛肉盖饭! 服务生对大厨师傅道:做一份土豆炖牛肉盖饭—— 大厨师傅道:土豆炖牛肉盖饭做好了…… 服务生把盖饭端上来,放到我所在的桌位。我看着香喷喷的土豆炖牛肉盖饭,举勺下口正要吃的时候,却突然发现这盘土豆炖牛肉盖饭变成了石头?! 这样的情况更是没有可能发生的!必定,现实生活不是《西游记》。必定,这篇文章是学术文章而不是《哈里波特》…… 问题后果:…… 如果上面这些荒唐的事情都成了现实,那么我肯定永远都不敢再来这家餐馆吃饭了。这些让我感到极大的不安。而在TraceLWord3这个项目中呢?似乎上面这些荒唐的事情都成真了。(我想,不仅仅是在TraceLWord3这样的项目中,作为这篇文章的读者,你是否也经历过像这一样荒唐的项目而全然未知呢?) 首先在ListLWord.aspx.cs文件 ... #048 private void LWord_DataBind() #049 { #050 DataSet ds=new DataSet(); #051 (new LWordService()).ListLWord(ds, @"LWordTable"); #052 #053 m_lwordListCtrl.DataSource=ds.Tables[@"LWordTable"].DefaultView; #054 m_lwordListCtrl.DataBind(); #055 } ...   在ListLWord.aspx.cs文件中,使用的是DataSet对象来取得留言板信息的。但是DataSet是不明确的!为什么这么说呢?行#051由LWordService填充的DataSet中可以集合任意的数据表DataTable,而在这些被收集的DataTable中,不一定会有一个是我们期望得到的。假设,LWordService类中的ListLWord函数其函数内容是: ... #006 namespace TraceLWord3.InterService #007 { ... #011 public class LWordService #012 { ... #019 public int ListLWord(DataSet ds, string tableName) #020 { #021 ds.Tables.Clear(); #022 ds.Tables.Add(new DataTable(tableName)); #023 #024 return 1; #025 } ... 函数中清除了数据集中所有的表之后,加入了一个新的数据表后就匆匆返回了。这样作的后果,会直接影响ListLWord.aspx。 ... #018 #019 #020
#021 <%# DataBinder.Eval(Container.DataItem, "PostTime") %> #022 <%# DataBinder.Eval(Container.DataItem, "TextContent") %> #023
#024
#025
... 这和前面提到的“情形一”,一模一样!我没有明确地提出自己想要的饭菜,但是餐馆服务生却揣摩我的意思,擅自作主。 其次,再看LWordService.cs文件 ... #019 public int ListLWord(DataSet ds, string tableName) #020 { #
/
本文档为【浅谈“三层结构”原理与用意】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索