浅谈“三层结构”原理与用意 8
#001 using system;
#002 using system.collections;
#003 using system.data;
#004 using system.data.oledb;
#005 using system.web;
#006
#007 using tracelword4.classes; // 引用实体规范层
#008
#009 namespace tracelword4.accesstask
#010 {
#011 /// <summary>
#012 /// lwordtask 留言板任务类
#013 /// </summary>
#014 public class lwordtask
#015 {
#016 // 数据库连接字符串
#017 private const string db_conn=@"provider=microsoft.jet.oledb.4.0;
data source=c:\dbfs\tracelworddb.mdb";
#018
#019 /// <summary>
#020 /// 读取 lword 数据表,返回留言对象数组
#021 /// </summary>
#022 /// <returns></returns>
#023 public lword[] listlword()
#024 {
#025 // 留言对象集合
#026 arraylist lwordlist=new arraylist();
#027
#028 string cmdtext="select * from [lword] order by [lwordid] desc";
#029
#030 oledbconnection dbconn=new oledbconnection(db_conn);
#031 oledbcommand dbcmd=new oledbcommand(cmdtext, dbconn);
#032
#033 try
#034 {
#035 dbconn.open();
#036 oledbdatareader dr=dbcmd.executereader();
#037
#038 while(dr.read())
#039 {
#040 lword lword=new lword();
#041
#042 // 设置留言编号
#043 lword.uniqueid=(int)dr["lwordid"];
#044 // 留言内容
#045 lword.textcontent=(string)dr["textcontent"];
#046 // 发送时间
#047 lword.posttime=(datetime)dr["posttime"];
#048
#049 // 加入留言对象到集合
#050 lwordlist.add(lword);
#051 }
#052 }
#053 catch
#054 {
注意这里,为了保证语义明确,使用了一步强制转型。
而不是直接返回arraylist对象
#055 throw;
#056 }
#057 finally
#058 {
#059 dbconn.close();
#060 }
#061
#062 // 将集合转型为数组并返回给调用者
#063 return (lword[])lwordlist.toarray(typeof(tracelword4.classes.lword));
#064 }
#065
#066 /// <summary>
#067 /// 发送留言信息到数据库
#068 /// </summary>
#069 /// <param name="newlword">留言对象</param>
#070 public void postlword(lword newlword)
#071 {
#072 // 留言内容不能为空
#073 if(newlword==null || newlword.textcontent==null || newlword.textcontent=="")
#074 throw new exception("留言内容为空");
#075
#076 string cmdtext="insert into [lword]([textcontent]) values(@textcontent)";
#077
#078 oledbconnection dbconn=new oledbconnection(db_conn);
#079 oledbcommand dbcmd=new oledbcommand(cmdtext, dbconn);
#080
#081 // 设置留言内容
#082 dbcmd.parameters.add(new oledbparameter("@textcontent",
oledbtype.longvarwchar));
#083 dbcmd.parameters["@textcontent"].value=newlword.textcontent;
#084
#085 try
#086 {
#087 dbconn.open();
#088 dbcmd.executenonquery();
#089 }
#090 catch
#091 {
#092 throw;
#093 }
#094 finally
#095 {
#096 dbconn.close();
#097 }
#098 }
#099 }
#100 }
这样,即便是将lwordtask.cs文件中的listlwords方法修改成访问[reguser]数据表的代码,也依然不会影响到外观层。因为函数只返回一个lword类型的数组。再有,因为位于外观层的重复器控件绑定的是lword类对象,而lword类中就必有对textcontent字段的定义。这样也就达到了规范数据访问层返回结果的目的。这便是为什么在duwamish7中会出现common项目的原因。不知道你现在看明白了么?而bincess.cn的做法和petshop3.0一样,是通过自定义类来达到实体规范层的目的!petshop3.0是通过modal项目,而bincess.cn则是通过classes项目。
餐馆又来了一位新大厨师傅——谈谈跨越数据库平台的问题
餐馆面积不大,但生意很火。每天吃饭的人都特别多。为了加快上菜的速度,所以餐馆又找来了一位新的大厨师傅。假如,tracelword4为了满足一部分用户对性能的较高需要,要其数据库能使用ms sql server 2000。那么我们该怎么办呢?数据库要从access 2000升迁到ms sqlserver 2000,那么只要集中修改accesstask项目中的程序文件就可以了。但是,我又不想让这样经典的留言板失去对access 2000数据库的支持。所以,正确的做法就是把原来所有的程序完整的拷贝一份放到另外的一个目录里。然后集中修改accesstask项目,使之可以支持ms sql server 2000。这样这个留言板就有了两个版本,一个是access 2000版本,另外一个就是ms sql server 2000版本……新的大厨师傅过来帮忙了,我们有必要让原来表现极佳的大厨师傅下课吗?可这样,新大厨师傅不是等于没来一样?新的大厨师傅过来帮忙了,我们有必要为新来的大厨师傅重新配备一套餐馆服务生系统、菜单系统吗?当然也没必要!那么,可不可以让tracelword4同时支持access 2000又支持ms sql server 2000呢?也就是说,不用完整拷贝原来的程序,而是在解决方案里加入一个新的项目,这个项目存放的是可以访问ms sql server 2000数据库的代码。然后,我们再通过一个“开关”来进行控制,当开关指向access 2000一端时,tracelword4就可以运行在access 2000数据库平台上,而如果开关指向ms sql server 2000那一端时,tracelword4就运行在ms sql server 2000数据库平台上……
在tracelword5中,加入了一个新项目sqlservertask,这个项目的代码是访问的ms sql server 2000数据库。还有一个新建的项目dalfactory,这个项目就是一个“开关”。这个“开关”项目中仅有一个dbtaskdriver.cs程序文件,就是用它来控制tracelword5到底运行载那个数据库平台上?
关于tracelword5,更完整的代码,可以在codepackage/tracelword5目录中找到——
dalfactory项目,其实就是“数据访问层工厂”,而dbtaskdriver类就是一个工厂类。也就是说dalfactory项目是“工厂模式”的一种应用。关于“工厂模式”,顾名思义,工厂是制造产品的地方,而“工厂模式”,就是通过“工厂类”来制造对象实例。“工厂类”可以通过给定的条件,动态地制造不同的对象实例。就好像下面这个样子:
// 水果基类
public class fruit;
// 苹果是一种水果
public class apple : fruit;
// 句子是一种水果
public class orange : fruit;
// 水果工厂类
public class fruitfactory
{
// 根据水果名称制造一个水果对象
public static fruit createinstance(string fruitname)
{
if(fruitname=="apple")
return new apple();
else if(fruitename=="orange")
return new orange();
else
return null;
}
}
// 制造一个apple对象,即:new apple();
apple anapple=(apple)fruitfactory.createinstance("apple");
// 制造一个orange对象,即:new orange();
orange anorange=(orange)fruitfactory.createinstance("orange");
工厂类制造对象实例,实际通常是要通过语言所提供的rtti(runtime type identification运行时类型识别)机制来实现。在visual c#.net中,是通过“反射”来实现的。它被封装在“system.reflection”名称空间下,通过c#反射,我们可以在程序运行期间动态地建立对象。关于c#.net反射,你可以到其它网站上搜索一下相关资料,这里就不详述了。左边是工厂模式的uml示意图。
新建的dbtaskdriver.cs文件,位于dalfactory项目中
#001 using system;
#002 using system.configuration;
#003 using system.reflection; // 需要使用 .net 反射
#004
#005 namespace tracelword5.dalfactory
#006 {
#007 /// <summary>
#008 /// dbtaskdriver 数据库访问层工厂
#009 /// </summary>
#010 public class dbtaskdriver
#011 {
#012 类 dbtaskdriver 构造器
#020
#021 /// <summary>
#022 /// 驱动数据库任务对象实例
#023 /// </summary>
#024 public object drivelwordtask()
#025 {
#026 // 获取程序集名称
#027 string assemblyname=configurationsettings.appsettings["assemblyname"];
#028 // 获取默认构造器名称
#029 string constructor=configurationsettings.appsettings["constructor"];
#030
#031 // 建立 accesstask 或者 sqlservertask 对象实例
#032 return assembly.load(assemblyname).createinstance(constructor, false);
#033 }
#034 }
#035 }
那么相应的,lwordservice.cs程序文件也要做相应的修改。