【转载】ASP实现的三层结构

来源:互联网 发布:淘宝首页登录 编辑:程序博客网 时间:2024/06/13 11:05
了解三层结构经典的文章!!!

看了大家的讨论,发觉很多人对数据库三层结构的概念还不很清楚,或者很想深入了解其中的奥秘。

为了满足大家的需求,我贴个帖子,希望对大家有所帮助。

不过实现三层结构的技术实在太多(MTSCORBACOMCOM+MIDAS等),我只能用其中一个较浅显的例子来说明问题。

请看: 

 

 

随着互连网应用的深入和发展,三层体系结构的应用模式也得到更多人的青睐。本文介绍了三层结构应用程序的概念和优点,并结合一个实例介绍了如何建立三层结构的ASP应用程序。 

 

 

一、两层结构的ASP应用有何缺点 

Browser/Server  应用程序开发领域,微软公司的IIS/ASP以其强大的功能,良好的扩展能力,及与其它微软产品的一致性,迅速地流行起来。它能使一个具有VB/VC经验的程序员,很快地成为一个Web程序员,开发出看上去非常专业的应用。但是,ASP有一个天生的缺点,就是ASP代码和HTML代码是混在一起的,ASP程序员既需要考虑与数据库打交道,需要关心如何与HTML配合,有时还需要用ASP直接生成HTML代码。其结果是,当程序逻辑足够复杂时,.asp源文件非常长;而且,无论客户提出用户界面的改变,还是商业逻辑的改变(比如,在考试系统中,"合格"的标准可能从达到60分就算合格,改为进入前100名才算合格),都需要对.asp文件进行改动,而商业逻辑的改变,很可能需要改动很多文件。 

 

 

二、三层结构的概念 

在传统的Client  /  Server应用中,也存在着上述同样的问题,多层结构的应用正是在对C/S  结构的总结基础上产生的,并且也已经扩展到了B/S应用开发领域。  即将应用划分为三层(可以有更多层,但三层最常见):  用户界面层,商业逻辑层,数据库层。  用户界面层负责处理用户的输入和向用户的输出,但并不负责解释其含义(出于效率的考虑,它可能在向上传输用户输入前进行合法性验证),这一层通常用前端工具(VBVCASP)开发;商业逻辑层是上下两层的纽带,它建立实际的数据库连接,根据用户的请求生成SQL语句检索或更新数据库,并把结果返回给客户端,这一层通常以动态链接库的形式存在并注册到服务器的注册簿(Registry)中,它与客户端通讯的接口符合某一特定的组件标准(COMCORBA),可以用任何支持这种标准的工具开发;数据库层负责实际的数据存储和检索。  有了这样的结构,上面的问题迎刃而解:还是以考试系统中的合格标准为例,在客户端所有需要显示合格人员名单的地方,调用这样一个函数GetQualifiedList,至于这个函数如何编写,如何与数据库打交道,以至访问的是何种数据库都与其无关(你一定有过这样的经历,在一种数据库系统上运行得很好的SQL语句,有时换到另一种数据库系统上必须加以修改);  在中间层DLL中实现这个GetQualifiedList函数,如果用户对"合格"的定义变了,只需要修改这个函数就可以了,只要此函数的入口参数和返回内容不变,在客户端不需作任何改动。在这里,我们看到了面向对象编程的特性之一封装性的优点,而这一点在开发大型应用时尤其有用--我们可以把开发人员分成两组,一组负责开发界面层,另一组负责开发商业逻辑层,双方只要按照事先商定的函数接口,并行地开发就可以,而不必向从前那样,后面的工作必须等前面的工作完成后才能开始。当然,这样的开发模式需要很好的项目协调和文档作支持。 

 

 

你也许会问,如果我把这些函数些在一个单独的文件中,再在需要调用的地方把它包含进来,不是同样能达到目的吗?  第一,这种方法效率不高,无论你把这些函数分散到多少个文件中,当你需要调用其中一个时,总会包含进一些实际上并不需要的函数,这无疑加重了服务器的负担,对服务器性能要求较高的Web应用尤其如此。  DLL只在需要时才调入内存且只调入需要的函数,并且多个应用程序实例可以共享同一个DLL实例;第二,设想一个员工,有20个属性(工号,姓名,年龄,性别......),现在给定某工号,要求返回此员工所有信息。此时如果单纯用函数,只能定义20个全局变量,在函数中改变这些变量值,或者定义一个有20个传参(by  reference)参数的函数。显然,第一种方法很麻烦而一旦增加一个属性后一种方法就需要更改函数接口。而在一个对象里,既包含成员方法(即函数和过程),也包括成员属性。如果我们采用对象的方法,则在函数中只需要改变对象的属性,在函数外可以直接引用改变了的对象属性值。  这种方法有些类似第一种方法,但1.属性值无需在函数外逐一说明;2.这些属性值只属于对象,与对象无关的代码不会无意地改变属性值;3.一旦对象被释放,这些值会被一起释放。 

 

三、如何开发三层结构的ASP应用程序 

ASP具有良好的扩充性,我们访问数据库时,采用的时ADO对象,访问文件时,采用的是文件系统对象(FSO),其实这时程序已经是三层结构的应用程序了,只不过由于是利用内置的对象而为意识到罢了。这些对象都遵循COM/ActiveX接口,因此我们自己开发的对象也要遵循这个接口。下面,我们就以上文提到的"合格"标准为例,演示如何创建自己的三层结构的ASP应用。 

1、在数据库系统中建立如下数据库表: 

Employee:  EMPLID  char  (5)  not  null 

Namechar  (10)  not  null 

Gender  char  (1)  not  null 

Score  int  not  null 

此表存储员工信息和考试成绩,为简单起见,这里只包含工号,姓名和性别三项,并且只有一门考试,EMPLID为主键。 

 

 

2、建立动态链接库 

 

 

启动VB(这里以VB为例,你可以用你喜欢的任何支持ActiveX接口的开发工具开发),新建一工程,工程类型为ActiveX  DLL。在工程中新建一个类,取名为Employee。你可以Class  Builder可视化的向类中填加属性和方法,也可以直接手工编辑。首先填加EMPLID属性如下: 

Private  msEMPLID  as  string 

Property  Let  EMPLID(sEMPLID  as  string) 

msEMPLID=sEMPLID 

End  Property 

Property  Get  EMPLID()  as  string 

EMPLID=msEMPLID 

End  Property 

一般地讲,每一个属性都应该有Property  LetProperty  Get两个方法,它们分别当向属性赋值和读取属性值时被调用。如果某个属性只被赋值而从不被读取(这种情况多发生在对应数据库表的主键的属性上),则Property  Get方法可以省略。Property  Let方法不能省略。你可以仿照上面的程序再建立NameGenderScore三个属性。然后创建如下方法: 

Public  Sub  Create(EMPLID  as  string) 

dim  conn  as  new  Connection 

dim  rs  as  new  Recordset 

dim  sql  as  string 

''Suppose  that  you  create  a  DSN  in  the  control  panel,  the  connectionstring  property 

''can  also  be  dsn-less  string 

conn.ConnectionString="dsn=dsnname;uid=username;password=pwd" 

conn.open 

sql="select  *  from  Employee  where  EMPLID=''"  &  EMPLID  &  "''" 

with  rs 

.open  sql,conn,1,3 

if  .eof  and  .bof  then 

exit  sub 

else 

msEMPLID=trim(.Fields("EMPLID")) 

msName=trim(.Fields("Name")) 

msGender=trim(.Fields("Gender")) 

msScore=.Fields("Score") 

end  if 

.close 

end  with 

set  rs=nothing 

conn.close 

set  conn=nothing 

End  Sub 

这里根据EMPLID创建Employee对象,注意数据库中的值是赋给三个私有变量,而不是直接赋值给属性,如果你单步调试就会发现,给msEMPLID赋值会调用Property  Let  EMPLID,也就是给属性赋值。 

  Sunnyfun

  [sunnyfun]

下面我们再创建一个类Employees,并填加如下方法: 

private  colQualifiedList  as  new  Collection 

private  mnCurrentIndex  as  integer 

Public  Sub  GetQualifiedList() 

dim  conn  as  new  Connection 

dim  rs  as  new  Recordset 

dim  sql  as  string 

''Suppose  that  you  create  a  DSN  in  the  control  panel,  the  connectionstring  property 

''can  also  be  dsn-less  string 

conn.ConnectionString="dsn=dsnname;uid=username;password=pwd" 

conn.open 

sql="select  EMPLID  from  Employee  where  Score>=60  order  by  Score  desc" 

with  rs 

.open  sql,conn,1,3 

if  .eof  and  .bof  then 

exit  sub 

else 

do  while  not  .eof 

dim  oEmployee  as  new  Employee 

oEmployee.Create  trim(.Fields("EMPLID")) 

colQualifiedList.Add  oEmployee 

set  oEmployee=nothing 

loop 

end  if 

.close 

end  with 

set  rs=nothing 

conn.close 

set  conn=nothing 

End  Sub 

首先请注意VB中创建类实例的语法dim  oEmployee  as  new  Employee,后面会看到,在ASP中创建类实例的语法是不同的。这个方法检索成绩大于等于60的员工工号,并据此创建一个Employee对象,再将此对象加入私有的集合对象中。下面两个函数遍历集合中的元素: 

Public  Function  GetFirst()  as  Employee 

if  colQualifiedList.count>0  then 

mnCurrentIndex=1 

set  GetFirst=colQualifiedList.Item(1) 

else 

set  GetFirst=nothing 

end  if 

End  Function 

Public  Function  GetNext()  as  Employee 

mnCurrentIndex=mnCurrentIndex+1 

if  mnCurrentIndex>colQualifiedList.count  then 

set  GetNext=nothing 

else 

set  GetNext=colQualifiedList.Item(mnCurrentIndex) 

End  if 

End  Function 

也许你会说,为何不把集合声明Public,这样在ASP中不是可以直接引用吗?确实,这样也行得通,编程实现起来也更简单些,但是,这样做破坏了封装性原则。因为数据以何格式存储完全是商业逻辑层的事,与用户界面层无关,假设有一天你因为每种原因放弃了用集合来存储数据的设计,而改用数组或记录集(Recordset)来存储,那你只需要修改GetFirstGetNext两个函数,用户界面层完全无需修改。 

至此类文件创建完毕,将工程文件存为  test.vbp,选File菜单下的Make  test.dll选项将其编译。 

3、注册动态链接库 

启动Web  Server  上的Microsoft  Transaction  Server  (Start--Windows  NT  Optionpack4--Internet  Information  Server--Internet  Service  Manager),展开Microsoft  Transaction  Server--Computer--My  Computer--Package  Installed,点鼠标右键选New--Package--Create  Empty  Package,输入包名Test(这里Test是任选的名字,不一定要与DLL同名)OK-Interactive  User-the  current  Logon  user--Finish。双击Test--Component,右键选Component-New-Component-Install  New  component(s)--  Add  File,选择你刚编译好的DLL文件,MTS会发现DLL中有两个类EmployeeEmployees。至此DLL注册完毕。 

 

 

4、编写ASP程序 

<HTML><Body> 

<p>Qualified  Employee  List</p> 

<table  border=1  cellspacing=0  cellpadding=0> 

<tr> 

<td>Employee  ID</td> 

<td>Name</td> 

<td>Gender</td> 

<td>Score</td> 

</tr> 

<% 

set  oEmployees=server.createobject("Test.Employees") 

oEmployees.GetQualifiedList 

set  oEmployee=oEmployees.GetFirst() 

do  while  not  oEmployee  is  nothing 

%> 

<tr> 

<td><%=oEmployee.EMPLID%></td> 

<td><%=oEmployee.Name%></td> 

<td><%=oEmployee.Gender%></td> 

<td><%=oEmployee.Score%></td> 

</tr> 

<% 

set  oEmployee=oEmployees.GetNext() 

loop 

%> 

</table> 

</body></html> 

注意在ASP中创建类实例的语法set  oEmployees=server.createobject("Test.Employees"),其中TestDLL的名字,Employees是类的名字;  当然,如果一个函数的返回值是一个对象,类似set  oEmployee=oEmployees.GetFirst()这样的语法也是可以的。 

 

 

 

 

至此,一个完整的三层结构的应用程序已经完成了,让我们看以下,如果把"合格"的定义改为:只有成绩进入前100名才算合格,程序需要做那些修改。事实上,如果你的数据库系统是SQL  Server,你只需把SQL语句改为: 

 

 

sql="select  top  100  EMPLID  from  Employee  order  by  Score  desc"  就已经可以了,即使为了跨数据库系统的兼容性,我们也只需要对GetQualifiedList做如下修改: 

sql="select  EMPLID  from  Employee  order  by  Score  desc" 

with  rs 

.open  sqlconn13 

if  .eof  and  .bof  then 

exit  sub 

else 

i=1 

do  while  (not  .eof)  and  (i<=100) 

dim  oEmployee  as  new  Employee 

oEmployee.Create  trim(.Fields("EMPLID")) 

colQualifiedList.Add  oEmployee 

set  oEmployee=nothing 

i=i+1 

loop 

end  if 

.close 

end  with 

.四、一些说明和注意事项 

1  由于这个例子比较简单,在Employee类中可以没有Create方法,而在Employees类中将员工的所有信息(工号,姓名,性别,成绩)都读出来并将其赋给Employee对象对应的属性。但在实际应用中,当Employee对象的属性增多,或表的数量增多,表之间关系变复杂时,还是本文所示的方法更有效,代码重用的机会更大。 

2、当DLL被修改后,在MTS中只能将其删除后重新注册,因为每次重新编译后在注册表中对象的ID值都将重新生成。 

3、从ASP中调用带参数的类方法和函数时,所有的变量参数一定要用相应的类型转换函数转换后再传入,否则会引起类型不匹配错误,因为VBScript中只有Variant类型,它不能自动转换成其它类型。例如,有如下的函数定义: 

Public  Function  Fun1(p1  as  stringp2  as  integer)  as  integer 

End  Function 

ASP程序中应如下调用: 

<% 

p1=obj.property1  ''  Property1  is  a  string  property 

p2=obj.property2  ''Property2  is  an  integer  property 

a=obj.Fun1(cstr(p1)cint(p2)) 

a=obj.Fun1("aaa"10)  ''  constant  parameter  need  not  be  changed 

%> 

而下面的两种写法是错误的: 

<% 

p1=obj.property1  ''  Property1  is  a  string  property 

p2=obj.property2  ''Property2  is  an  integer  property 

a=obj.Fun1(p1p2)  ''  incorrectp1  and  p2  are  variant  variables 

p1=cstr(p1) 

p2=cint(p2) 

a=obj.Fun1(p1p2)  ''  still  incorrect 

%> 

这里第二种写法仍然是错误的,即使经过了类型转换,p1p2仍然是Variant变量。在VBScript中,数据类型和类型转换函数只在表达式运算中起作用,变量只有Variant一种类型。