餐馆又来了一位新大厨师傅——谈谈跨越数据库平台的问题
餐馆面积不大,但生意很火。每天吃饭的人都特别多。为了加快上菜的速度,所以餐馆又找来了一位新的大厨师傅。假如,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程序文件也要做相应的修改。
#001 using System;
#002 using System.Data;
#003
#004 using TraceLWord5.AccessTask;
#005 using TraceLWord5.Classes; // 引用实体规范层
#006 using TraceLWord5.DALFactory; // 引用数据访问层工厂
#007 using TraceLWord5.SqlServerTask;
#008
#009 namespace TraceLWord5.InterService
#010 {
...
#014 public class LWordService
#015 {
...
#020 public LWord[] ListLWord()
#021 {
#022 object dbTask=(new DbTaskDriver()).DriveLWordTask();
#023
#024 // 留言板运行在 Access 数据库平台上
#025 if(dbTask is AccessTask.LWordTask)
#026 return ((AccessTask.LWordTask)dbTask).ListLWord();
#027
#028 // 留言板运行在 MS SQL Server 数据库平台上
#029 if(dbTask is SqlServerTask.LWordTask)
#030 return ((SqlServerTask.LWordTask)dbTask).GetLWords();
#031
#032 return null;
#033 }
...
#039 public void PostLWord(LWord newLWord)
#040 {
#041 object dbTask=(new DbTaskDriver()).DriveLWordTask();
#042
#043 // 留言板运行在 Access 数据库平台上
#044 if(dbTask is AccessTask.LWordTask)
#045 ((AccessTask.LWordTask)dbTask).PostLWord(newLWord);
#046
#047 // 留言板运行在 MS SQL Server 数据库平台上
#048 if(dbTask is SqlServerTask.LWordTask)
#049 ((SqlServerTask.LWordTask)dbTask).AddNewLWord(newLWord);
#050 }
#051 }
#052 }
原来的AccessTask项目及程序文件不需要变化,只是多加了一个SqlServerTask项目。新项目中,也有一个LWordTask.cs程序文件,其内容是:
#001 using System;
#002 using System.Collections;
#003 using System.Data;
#004 using System.Data.SqlClient; // 需要访问 MS SQL Server 数据库
#005 using System.Web;
#006
#007 using TraceLWord5.Classes; // 引用实体规范层
#008
#009 namespace TraceLWord5.SqlServerTask
#010 {
#011 /// <summary>
#012 /// LWordTask 留言板任务类
#013 /// </summary>
#014 public class LWordTask
#015 {
#016 // 数据库连接字符串
#017 private const string DB_CONN=@"Server=127.0.0.1; uid=sa; pwd=;
DataBase=TraceLWordDb";
#018
#019 /// <summary>
#020 /// 读取 LWord 数据表,返回留言对象数组
#021 /// </summary>
#022 /// <returns></returns>
#023 public LWord[] GetLWords()
#024 {
#025 // 留言对象集合
#026 ArrayList lwordList=new ArrayList();
#027
#028 string cmdText="SELECT * FROM [LWord] ORDER BY [LWordID] DESC";
#029
#030 SqlConnection dbConn=new SqlConnection(DB_CONN);
#031 SqlCommand dbCmd=new SqlCommand(cmdText, dbConn);
#032
#033 try
#034 {
#035 dbConn.Open();
#036 SqlDataReader 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 {
#055 throw;
#056 }
#057 finally
#058 {
#059 dbConn.Close();
#060 }
#061
#062 // 将集合转型为数组并返回给调用者
#063 return (LWord[])lwordList.ToArray(typeof(TraceLWord5.Classes.LWord));
#064 }
#065
#066 /// <summary>
#067 /// 发送留言信息到数据库
#068 /// </summary>
#069 /// <param name="newLWord">留言对象</param>
#070 public void AddNewLWord(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 SqlConnection dbConn=new SqlConnection(DB_CONN);
#079 SqlCommand dbCmd=new SqlCommand(cmdText, dbConn);
#080
#081 // 设置留言内容
#082 dbCmd.Parameters.Add(new SqlParameter("@TextContent", SqlDbType.NText));
#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 }
特别指出的是,这个SqlServerTask中的LWordTask程序文件,也遵循“土豆炖牛肉盖饭”式的强制标准!
在TraceLWord5中,也需要配置Web.Config文件,需要加入自定义的键值:
#001 <?xml version="1.0" encoding="utf-8" ?>
#002 <configuration>
#003
#004 <system.web>
#005 <identity impersonate="true" />
#006 <compilation defaultLanguage="c#" debug="true" />
#007 <customErrors mode="RemoteOnly" />
#008 </system.web>
#009
#010 <appSettings>
...
#026 <!--// SQLServer 2000 数据库任务程序集及驱动类名称 //-->
#027 <add key="AssemblyName"
#028 value="TraceLWord5.SqlServerTask" />
#029 <add key="Constructor"
#030 value="TraceLWord5.SqlServerTask.LWordTask" />
#031
#032 </appSettings>
#033
#034 </configuration>
通过修改配置文件中的关键信息,就可以修改留言板的数据库运行平台。这样便做到了跨数据库平台的目的。
用户在访问TraceLWord5的ListLWord.aspx页面时序图:
当一个用户访问TraceLWord5的ListLWord.aspx页面的时候,会触发该页面后台程序中的Page_Load函数。而在该函数中调用了LWord_DataBind函数来获取留言板信息。由图中可以看到出,LWord_DataBind在被调用的期间,会建立一个新的LWordService类对象,并调用这个对象的ListLWord函数。在LWordService.ListLWord函数被调用的期间,会建立一个新的DALFactory.DbTaskDriver类对象,并调用这个对象的DriveLWordTask函数来建立一个真正的数据访问层对象。在代码中,DriveLWordTask函数需要读取应用程序配置文件。当一个真正的数据访问层类对象被建立之后,会返给调用者LWordService.ListLWord,调用者会继续调用这个真正的数据访问层类对象的GetLWords函数,最终取到留言板数据。PostLWord.aspx页面时序图,和上面这个差不多。就是这样,经过一层又一层的调用,来获取返回结果或是保存数据。
注意:从时序图中可以看出,当子程序模块未执行结束时,主程序模块只能处于等待状态。这说明将应用程序划分层次,会带来其执行速度上的一些损失……
烹制土豆烧牛肉盖饭的方法论
TraceLWord5已经实现了跨数据库平台的目的。但是稍微细心一点就不难发现,TraceLWord5有一个很致命的缺点。那就是如果要加入对新的数据库平台的支持,除去必要的新建数据访问层项目以外,还要在中间业务层InsetService项目中添加相应的依赖关系和代码。例如,新加入了对Oracle9i的数据库支持,那么除去要新建一个OracleTask项目以外,还要在LWordService中添加对OracleTask项目的依赖关系,并增加代码如下:
...
#020 public LWord[] ListLWord()
#021 {
#022 object dbTask=(new DbTaskDriver()).DriveLWordTask();
#023
#024 // 留言板运行在 Access 数据库平台上
#025 if(dbTask is AccessTask.LWordTask)
#026 return ((AccessTask.LWordTask)dbTask).ListLWord();
#027
#028 // 留言板运行在 MS SQL Server 数据库平台上
#029 if(dbTask is SqlServerTask.LWordTask)
#030 return ((SqlServerTask.LWordTask)dbTask).GetLWords();
#031
#032 // 留言板运行在 Oracle 数据库平台上
#033 if(dbTask is OracleTask.LWordTask)
#034 return ((OracleTask.LWordTask)dbTask).FetchLWords();
#035
#036 return null;
#037 }
#038
...
每加入对新数据库的支持,就要修改中间业务层,这是件很麻烦的事情。再有就是,这三个数据访问层,获取留言板信息的方法似乎是各自为政,没有统一的标准。在AccessTask项目中使用的是ListLWord函数来获取留言信息;而在SqlServerTask项目中则是使用GetLWords函数来获取;再到了OracleTask又是换成了FetchLWords……
餐馆服务生也许会对新来的大厨师傅很感兴趣,或许也会对新来的大厨师傅的手艺很感兴趣。但是这些餐馆服务生,绝对不会去背诵哪位大厨师傅会做什么样的菜,哪位大厨师傅不会做什么样的菜?也不会去在意同样的一道菜肴,两位大厨师傅不同的烹制步骤是什么?对于我所点的“土豆炖牛肉盖饭”,餐馆服务生只管对着厨房大声叫道:“土豆炖牛盖饭一份!”,饭菜马上就会做好。至于是哪个厨师做出来的,服务生并不会关心。其实服务生的意思是说:“外面有个顾客要吃‘土豆炖牛肉盖饭’,你们两个大厨师傅,哪位会做这个,马上给做一份……”。如果新来的大厨师傅不会做,那么原来的大厨师傅会担起此重任。如果新来的大厨师傅会做,那么两个大厨师傅之间谁现在更悠闲一些就由谁来做。
在TraceLWord5中,两个数据访问层,都可以获取和保存留言信息,只是他们各自的函数名称不一样。但是对于中间业务层,却必须详细的记录这些,这似乎显得有些多余。仅仅是为了顺利的完成TraceLWord5这个“大型项目”,负责中间业务层的程序员要和负责数据访问层的程序员进行额外的沟通。TraceLWord5中,一个真正的数据访问层对象实例,是由DALFactory名称空间中的DbTaskDriver类制造的。如果中间业务层只需要知道“这个真正的数据访问层对象实例”有能力获取留言板和存储留言板,而不用关心其内部实现,那么就不会随着数据访问层项目的增加,而修改中间业务层了。换句直白的话来说就是:如果所有的数据访问层对象实例,都提供统一的函数名称“ListLWord函数”和“PostLWord函数”,那么中间业务层就不需要判断再调用了。我们需要“烹制土豆烧牛肉盖饭的方法论”的统一!——
Word教程网 | Excel教程网 | Dreamweaver教程网 | Fireworks教程网 | PPT教程网 | FLASH教程网 | PS教程网 |
HTML教程网 | DIV CSS教程网 | FLASH AS教程网 | ACCESS教程网 | SQL SERVER教程网 | C语言教程网 | JAVASCRIPT教程网 |
ASP教程网 | ASP.NET教程网 | CorelDraw教程网 |