天天看點

建立三層結構的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應用開發領域。 即将應用劃分為三層(可以有更多層,但三層最常見): 使用者界面層,商業邏輯層,資料庫層。 使用者界面層負責處理使用者的輸入和向使用者的輸出,但并不負責解釋其含義(出于效率的考慮,它可能在向上傳輸使用者輸入前進行合法性驗證),這一層通常用前端工具(VB,VC,ASP等)開發;商業邏輯層是上下兩層的紐帶,它建立實際的資料庫連接配接,根據使用者的請求生成SQL語句檢索或更新資料庫,并把結果傳回給用戶端,這一層通常以動态連結庫的形式存在并注冊到伺服器的注冊簿(Registry)中,它與用戶端通訊的接口符合某一特定的元件标準(如COM,CORBA),可以用任何支援這種标準的工具開發;資料庫層負責實際的資料存儲和檢索。 有了這樣的結構,上面的問題迎刃而解:還是以考試系統中的合格标準為例,在用戶端所有需要顯示合格人員名單的地方,調用這樣一個函數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,

          Name  char (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

  一般地講,每一個屬性都應該有Property Let和Property Get兩個方法,它們分别當向屬性指派和讀取屬性值時被調用。如果某個屬性隻被指派而從不被讀取(這種情況多發生在對應資料庫表的主鍵的屬性上),則Property Get方法可以省略。Property Let方法不能省略。你可以仿照上面的程式再建立Name,Gender和Score三個屬性。然後建立如下方法:

  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,也就是給屬性指派。

  下面我們再建立一個類Employees,并填加如下方法:

  private colQualifiedList as new Collection

  private mnCurrentIndex as integer

  Public Sub GetQualifiedList()

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

     do while not .eof

       dim oEmployee as new Employee

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

       colQualifiedList.Add oEmployee

       set oEmployee=nothing

     loop

  首先請注意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)

     set GetFirst=nothing

  End Function

  Public Function GetNext() as Employee

   mnCurrentIndex=mnCurrentIndex+1

   if mnCurrentIndex>colQualifiedList.count then

     set GetNext=nothing

     set GetNext=colQualifiedList.Item(mnCurrentIndex)

   End if

  也許你會說,為何不把集合聲明Public,這樣在ASP中不是可以直接引用嗎?确實,這樣也行得通,程式設計實作起來也更簡單些,但是,這樣做破壞了封裝性原則。因為資料以何格式存儲完全是商業邏輯層的事,與使用者界面層無關,假設有一天你因為每種原因放棄了用集合來存儲資料的設計,而改用數組或記錄集(Recordset)來存儲,那你隻需要修改GetFirst和GetNext兩個函數,使用者界面層完全無需修改。

  至此類檔案建立完畢,将工程檔案存為 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中有兩個類Employee和Employees。至此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

  %>

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

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

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

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

     set oEmployee=oEmployees.GetNext()

   loop

  </table>

  </body></html>

  注意在ASP中建立類執行個體的文法set oEmployees=server.createobject("Test.Employees"),其中Test是DLL的名字,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"

   .open sql,conn,1,3

     i=1

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

       i=i+1

  ...

  然後把DLL重新編譯,注冊就可以了,ASP程式完全不必修改。

四.一些說明和注意事項

  1. 由于這個例子比較簡單,在Employee類中可以沒有Create方法,而在Employees類中将員工的所有資訊(工号,姓名,性别,成績)都讀出來并将其賦給Employee對象對應的屬性。但在實際應用中,當Employee對象的屬性增多,或表的數量增多,表之間關系變複雜時,還是本文所示的方法更有效,代碼重用的機會更大。

  2. 當DLL被修改後,在MTS中隻能将其删除後重新注冊,因為每次重新編譯後在系統資料庫中對象的ID值都将重新生成。

  3. 從ASP中調用帶參數的類方法和函數時,所有的變量參數一定要用相應的類型轉換函數轉換後再傳入,否則會引起類型不比對錯誤,因為VBScript中隻有Variant類型,它不能自動轉換成其它類型。例如,有如下的函數定義:

  Public Function Fun1(p1 as string,p2 as integer) as integer

  在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

  而下面的兩種寫法是錯誤的:

   a=obj.Fun1(p1,p2) " incorrect,p1 and p2 are variant variables

   p1=cstr(p1)

   p2=cint(p2)

   a=obj.Fun1(p1,p2) " still incorrect

  這裡第二種寫法仍然是錯誤的,即使經過了類型轉換,p1和p2仍然是Variant變量。在VBScript中,資料類型和類型轉換函數隻在表達式運算中起作用,變量隻有Variant一種類型。

結束語

  以上對多層結構的理論和實踐進行了一番探讨,希望能對您的開發有所幫助。這裡還有一個問題,即類和類的成員該如何設計。這既涉及面向對象程式設計的理論,也需要一定的實踐經驗。請參考相關的OOP理論書籍并在實踐中不斷總結,相信您一定能設計出自己的完美的多層結構的應用程式。