继续————>
要给大家讲个故事了 会有利于我们理解
对“三层结构”的深入理解——从一家小餐馆说起
一个“三层结构”的Web应用程序,就好象是一家小餐馆。
n 表 现 层,所有的.aspx页面就好像是这家餐馆的菜谱。
n 中间业务层,就像是餐馆的服务生。
n 数据访问层,就像是餐馆的大厨师傅。
n 而我们这些网站浏览者,就是去餐馆吃饭的吃客了……
我们去一家餐馆吃饭,首先得看他们的菜谱,然后唤来服务生,告诉他我们想要吃的菜肴。服务生记下来以后,便会马上去通知大厨师傅要烹制这些菜。大厨师傅收到通知后,马上起火烧菜。过了不久,服务生便把一道一道香喷喷的、热气腾腾的美味端到我们的桌位上——
而我们访问一个基于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 <asp:DataList ID="m_lwordListCtrl" Runat="Server">
#019 <ItemTemplate>
#020 <div> <!--// 会提示找不到下面这两个字段 //-->
#021 <%# DataBinder.Eval(Container.DataItem, "PostTime") %>
#022 <%# DataBinder.Eval(Container.DataItem, "TextContent") %>
#023 </div>
#024 </ItemTemplate>
#025 </asp:DataList>
...
这和前面提到的“情形一”,一模一样!我没有明确地提出自己想要的饭菜,但是餐馆服务生却揣摩我的意思,擅自作主。
其次,再看LWordService.cs文件
...
#019 public int ListLWord(DataSet ds, string tableName)
#020 {
#021 return (new LWordTask()).ListLWord(ds, tableName);
#022 }
...
在LWordService.cs文件中,也是使用DataSet对象来取得留言板信息的。这个DataSet同样的不明确,含糊不清的指令还在执行……行#021由LWordTask填充的DataSet不一定会含有我们希望得到的表。即便是行#019中的DataSet参数已经明确的定义了每个表的结构,那么在带入行#021之后,可能也会变得混淆。例如,LWordTask类中的ListLWord函数其函数内容是:
...
#006 namespace TraceLWord2
#007 {
...
#011 public class LWordTask
#012 {
...
#022 public int ListLWord(DataSet ds, string tableName)
#023 {
#024 ds.Tables.Clear();
#025
#026 // 在SQL语句里选取了 [RegUser] 表而非 [LWord] 表
#027 string cmdText="SELECT * FROM [RegUser] ORDER BY [RegUserID] DESC";
#028
#029 OleDbConnection dbConn=new OleDbConnection(DB_CONN);
#030 OleDbDataAdapter dbAdp=new OleDbDataAdapter(cmdText, dbConn);
#031
#032 int count=dbAdp.Fill(ds, tableName);
#033
#034 return count;
#035 }
...
函数中清除了数据集中所有的表之后,选取了注册用户数据表[RegUser]对DataSet进行填充并返回。也就是说,即便是LWordService.cs文件中行#019中的DataSet参数已经明确的定义了每个表的结构,也可能会出现和前面提到的和“情形三”一样结果。
最后,再看看LWordTask.cs文件
...
#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 }
...
看到这里,我感到很是欣慰!我只能说我们的大厨师傅是一个厚道的人,而且还是很知我心的人。
我们不能只坐在那里期盼着我们的程序会往好的方向发展,这样很被动。写出上面的这些程序段,必须小心翼翼。就连数据库表中的字段命名都要一审再审。一旦变化,就直接影响到位于“表现层”的ListLWord.aspx文件。仅仅是为了顺利的完成TraceLWord3这个“大型项目”,页面设计师要和程序员还有数据库管理员要进行额外的沟通。我们需要一个“土豆炖牛肉盖饭”式的强制标准!——
引入实体规范
为了达到一种“土豆炖牛肉盖饭”式的强制标准,所以在TraceLWord4中,引入了Classes项目。在这个项目里,只有一个LWord.cs程序文件。这是一个非常重要的文件,它属于“实体规范层”,如果是在一个Java项目中,Classes可以看作是:“实体Bean”。更完整的代码,可以在CodePackage/TraceLWord4目录中找到——
LWord.cs文件内容如下:
#001 using System;
#002
#003 namespace TraceLWord4.Classes
#004 {
#005 /// <summary>
#006 /// LWord 留言板类定义
#007 /// </summary>
#008 public class LWord
#009 {
#010 // 编号
#011 private int m_uniqueID;
#012 // 文本内容
#013 private string m_textContent;
#014 // 发送时间
#015 private DateTime m_postTime;
#016
#017 #region 类 LWord 构造器
#018 /// <summary>
#019 /// 类 LWord 默认构造器
#020 /// </summary>
#021 public LWord()
#022 {
#023 }
#024
#025 /// <summary>
#026 /// 类 LWord 参数构造器
#027 /// </summary>
#028 /// <param name="uniqueID">留言编号</param>
#029 public LWord(int uniqueID)
#030 {
#031 this.UniqueID=uniqueID;
#032 }
#033 #endregion
#034
#035 /// <summary>
#036 /// 设置或获取留言编号
#037 /// </summary>
#038 public int UniqueID
#039 {
#040 set
#041 {
#042 this.m_uniqueID=(value<=0 ? 0 : value);
#043 }
#044
#045 get
#046 {
#047 return this.m_uniqueID;
#048 }
#049 }
#050
#051 /// <summary>
#052 /// 设置或获取留言内容
#053 /// </summary>
#054 public string TextContent
#055 {
#056 set
#057 {
#058 this.m_textContent=value;
#059 }
#060
#061 get
#062 {
#063 return this.m_textContent;
#064 }
#065 }
#066
#067 /// <summary>
#068 /// 设置或获取发送时间
#069 /// </summary>
#070 public DateTime PostTime
#071 {
#072 set
#073 {
#074 this.m_postTime=value;
#075 }
#076
#077 get
#078 {
#079 return this.m_postTime;
#080 }
#081 }
#082 }
#083 }
这个强制标准,LWordService和LWordTask都必须遵守!所以LWordService相应的要做出变化:
呵呵 准备下一章了
请关注三层结构”原理与用意“ (四)
祝君早日成功!