淺談怎么創(chuàng)建3層體系結(jié)構(gòu)的ASP應(yīng)用程序
發(fā)表時(shí)間:2023-08-11 來(lái)源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]隨著互連網(wǎng)應(yīng)用的深入和發(fā)展,三層體系結(jié)構(gòu)的應(yīng)用模式也得到更多人的青睞。本文介紹了三層結(jié)構(gòu)應(yīng)用程序的概念和優(yōu)點(diǎn),并結(jié)合一個(gè)實(shí)例介紹了如何建立三層結(jié)構(gòu)的ASP應(yīng)用程序。 一、兩層結(jié)構(gòu)的ASP應(yīng)用有何缺點(diǎn)...
隨著互連網(wǎng)應(yīng)用的深入和發(fā)展,三層體系結(jié)構(gòu)的應(yīng)用模式也得到更多人的青睞。本文介紹了三層結(jié)構(gòu)應(yīng)用程序的概念和優(yōu)點(diǎn),并結(jié)合一個(gè)實(shí)例介紹了如何建立三層結(jié)構(gòu)的ASP應(yīng)用程序。
一、兩層結(jié)構(gòu)的ASP應(yīng)用有何缺點(diǎn)
在Browser/Server 應(yīng)用程序開(kāi)發(fā)領(lǐng)域,微軟公司的IIS/ASP以其強(qiáng)大的功能,良好的擴(kuò)展能力,及與其它微軟產(chǎn)品的一致性,迅速地流行起來(lái)。它能使一個(gè)具有VB/VC經(jīng)驗(yàn)的程序員,很快地成為一個(gè)Web程序員,開(kāi)發(fā)出看上去非常專(zhuān)業(yè)的應(yīng)用。但是,ASP有一個(gè)天生的缺點(diǎn),就是ASP代碼和HTML代碼是混在一起的,ASP程序員既需要考慮與數(shù)據(jù)庫(kù)打交道,需要關(guān)心如何與HTML配合,有時(shí)還需要用ASP直接生成HTML代碼。其結(jié)果是,當(dāng)程序邏輯足夠復(fù)雜時(shí),.asp源文件非常長(zhǎng);而且,無(wú)論客戶(hù)提出用戶(hù)界面的改變,還是商業(yè)邏輯的改變(比如,在考試系統(tǒng)中,"合格"的標(biāo)準(zhǔn)可能從達(dá)到60分就算合格,改為進(jìn)入前100名才算合格),都需要對(duì).asp文件進(jìn)行改動(dòng),而商業(yè)邏輯的改變,很可能需要改動(dòng)很多文件。
二、三層結(jié)構(gòu)的概念
在傳統(tǒng)的Client / Server應(yīng)用中,也存在著上述同樣的問(wèn)題,多層結(jié)構(gòu)的應(yīng)用正是在對(duì)C/S 結(jié)構(gòu)的總結(jié)基礎(chǔ)上產(chǎn)生的,并且也已經(jīng)擴(kuò)展到了B/S應(yīng)用開(kāi)發(fā)領(lǐng)域。 即將應(yīng)用劃分為三層(可以有更多層,但三層最常見(jiàn)): 用戶(hù)界面層,商業(yè)邏輯層,數(shù)據(jù)庫(kù)層。 用戶(hù)界面層負(fù)責(zé)處理用戶(hù)的輸入和向用戶(hù)的輸出,但并不負(fù)責(zé)解釋其含義(出于效率的考慮,它可能在向上傳輸用戶(hù)輸入前進(jìn)行合法性驗(yàn)證),這一層通常用前端工具(VB,VC,ASP等)開(kāi)發(fā);商業(yè)邏輯層是上下兩層的紐帶,它建立實(shí)際的數(shù)據(jù)庫(kù)連接,根據(jù)用戶(hù)的請(qǐng)求生成SQL語(yǔ)句檢索或更新數(shù)據(jù)庫(kù),并把結(jié)果返回給客戶(hù)端,這一層通常以動(dòng)態(tài)鏈接庫(kù)的形式存在并注冊(cè)到服務(wù)器的注冊(cè)簿(Registry)中,它與客戶(hù)端通訊的接口符合某一特定的組件標(biāo)準(zhǔn)(如COM,CORBA),可以用任何支持這種標(biāo)準(zhǔn)的工具開(kāi)發(fā);數(shù)據(jù)庫(kù)層負(fù)責(zé)實(shí)際的數(shù)據(jù)存儲(chǔ)和檢索。 有了這樣的結(jié)構(gòu),上面的問(wèn)題迎刃而解:還是以考試系統(tǒng)中的合格標(biāo)準(zhǔn)為例,在客戶(hù)端所有需要顯示合格人員名單的地方,調(diào)用這樣一個(gè)函數(shù)GetQualifiedList,至于這個(gè)函數(shù)如何編寫(xiě),如何與數(shù)據(jù)庫(kù)打交道,以至訪問(wèn)的是何種數(shù)據(jù)庫(kù)都與其無(wú)關(guān)(你一定有過(guò)這樣的經(jīng)歷,在一種數(shù)據(jù)庫(kù)系統(tǒng)上運(yùn)行得很好的SQL語(yǔ)句,有時(shí)換到另一種數(shù)據(jù)庫(kù)系統(tǒng)上必須加以修改); 在中間層DLL中實(shí)現(xiàn)這個(gè)GetQualifiedList函數(shù),如果用戶(hù)對(duì)"合格"的定義變了,只需要修改這個(gè)函數(shù)就可以了,只要此函數(shù)的入口參數(shù)和返回內(nèi)容不變,在客戶(hù)端不需作任何改動(dòng)。在這里,我們看到了面向?qū)ο缶幊痰奶匦灾环庋b性的優(yōu)點(diǎn),而這一點(diǎn)在開(kāi)發(fā)大型應(yīng)用時(shí)尤其有用--我們可以把開(kāi)發(fā)人員分成兩組,一組負(fù)責(zé)開(kāi)發(fā)界面層,另一組負(fù)責(zé)開(kāi)發(fā)商業(yè)邏輯層,雙方只要按照事先商定的函數(shù)接口,并行地開(kāi)發(fā)就可以,而不必向從前那樣,后面的工作必須等前面的工作完成后才能開(kāi)始。當(dāng)然,這樣的開(kāi)發(fā)模式需要很好的項(xiàng)目協(xié)調(diào)和文檔作支持。
你也許會(huì)問(wèn),如果我把這些函數(shù)些在一個(gè)單獨(dú)的文件中,再在需要調(diào)用的地方把它包含進(jìn)來(lái),不是同樣能達(dá)到目的嗎? 第一,這種方法效率不高,無(wú)論你把這些函數(shù)分散到多少個(gè)文件中,當(dāng)你需要調(diào)用其中一個(gè)時(shí),總會(huì)包含進(jìn)一些實(shí)際上并不需要的函數(shù),這無(wú)疑加重了服務(wù)器的負(fù)擔(dān),對(duì)服務(wù)器性能要求較高的Web應(yīng)用尤其如此。 而DLL只在需要時(shí)才調(diào)入內(nèi)存且只調(diào)入需要的函數(shù),并且多個(gè)應(yīng)用程序?qū)嵗梢怨蚕硗粋(gè)DLL實(shí)例;第二,設(shè)想一個(gè)員工,有20個(gè)屬性(工號(hào),姓名,年齡,性別......),現(xiàn)在給定某工號(hào),要求返回此員工所有信息。此時(shí)如果單純用函數(shù),只能定義20個(gè)全局變量,在函數(shù)中改變這些變量值,或者定義一個(gè)有20個(gè)傳參(by reference)參數(shù)的函數(shù)。顯然,第一種方法很麻煩而一旦增加一個(gè)屬性后一種方法就需要更改函數(shù)接口。而在一個(gè)對(duì)象里,既包含成員方法(即函數(shù)和過(guò)程),也包括成員屬性。如果我們采用對(duì)象的方法,則在函數(shù)中只需要改變對(duì)象的屬性,在函數(shù)外可以直接引用改變了的對(duì)象屬性值。 這種方法有些類(lèi)似第一種方法,但1.屬性值無(wú)需在函數(shù)外逐一說(shuō)明;2.這些屬性值只屬于對(duì)象,與對(duì)象無(wú)關(guān)的代碼不會(huì)無(wú)意地改變屬性值;3.一旦對(duì)象被釋放,這些值會(huì)被一起釋放。
三、如何開(kāi)發(fā)三層結(jié)構(gòu)的ASP應(yīng)用程序
ASP具有良好的擴(kuò)充性,我們?cè)L問(wèn)數(shù)據(jù)庫(kù)時(shí),采用的時(shí)ADO對(duì)象,訪問(wèn)文件時(shí),采用的是文件系統(tǒng)對(duì)象(FSO),其實(shí)這時(shí)程序已經(jīng)是三層結(jié)構(gòu)的應(yīng)用程序了,只不過(guò)由于是利用內(nèi)置的對(duì)象而為意識(shí)到罷了。這些對(duì)象都遵循COM/ActiveX接口,因此我們自己開(kāi)發(fā)的對(duì)象也要遵循這個(gè)接口。下面,我們就以上文提到的"合格"標(biāo)準(zhǔn)為例,演示如何創(chuàng)建自己的三層結(jié)構(gòu)的ASP應(yīng)用。
1、在數(shù)據(jù)庫(kù)系統(tǒng)中建立如下數(shù)據(jù)庫(kù)表:
Employee: EMPLID char (5) not null,
Name char (10) not null,
Gender char (1) not null,
Score int not null
此表存儲(chǔ)員工信息和考試成績(jī),為簡(jiǎn)單起見(jiàn),這里只包含工號(hào),姓名和性別三項(xiàng),并且只有一門(mén)考試,EMPLID為主鍵。
2、建立動(dòng)態(tài)鏈接庫(kù)
啟動(dòng)VB(這里以VB為例,你可以用你喜歡的任何支持ActiveX接口的開(kāi)發(fā)工具開(kāi)發(fā)),新建一工程,工程類(lèi)型為ActiveX DLL。在工程中新建一個(gè)類(lèi),取名為Employee。你可以Class Builder可視化的向類(lèi)中填加屬性和方法,也可以直接手工編輯。首先填加EMPLID屬性如下:
Private msEMPLID as string
Property Let EMPLID(sEMPLID as string)
msEMPLID=sEMPLID
End Property
Property Get EMPLID() as string
EMPLID=msEMPLID
End Property
一般地講,每一個(gè)屬性都應(yīng)該有Property Let和Property Get兩個(gè)方法,它們分別當(dāng)向?qū)傩再x值和讀取屬性值時(shí)被調(diào)用。如果某個(gè)屬性只被賦值而從不被讀取(這種情況多發(fā)生在對(duì)應(yīng)數(shù)據(jù)庫(kù)表的主鍵的屬性上),則Property Get方法可以省略。Property Let方法不能省略。你可以仿照上面的程序再建立Name,Gender和Score三個(gè)屬性。然后創(chuàng)建如下方法:
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
這里根據(jù)EMPLID創(chuàng)建Employee對(duì)象,注意數(shù)據(jù)庫(kù)中的值是賦給三個(gè)私有變量,而不是直接賦值給屬性,如果你單步調(diào)試就會(huì)發(fā)現(xiàn),給msEMPLID賦值會(huì)調(diào)用Property Let EMPLID,也就是給屬性賦值。
下面我們?cè)賱?chuàng)建一個(gè)類(lèi)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
首先請(qǐng)注意VB中創(chuàng)建類(lèi)實(shí)例的語(yǔ)法dim oEmployee as new Employee,后面會(huì)看到,在ASP中創(chuàng)建類(lèi)實(shí)例的語(yǔ)法是不同的。這個(gè)方法檢索成績(jī)大于等于60的員工工號(hào),并據(jù)此創(chuàng)建一個(gè)Employee對(duì)象,再將此對(duì)象加入私有的集合對(duì)象中。下面兩個(gè)函數(shù)遍歷集合中的元素:
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
也許你會(huì)說(shuō),為何不把集合聲明Public,這樣在ASP中不是可以直接引用嗎?確實(shí),這樣也行得通,編程實(shí)現(xiàn)起來(lái)也更簡(jiǎn)單些,但是,這樣做破壞了封裝性原則。因?yàn)閿?shù)據(jù)以何格式存儲(chǔ)完全是商業(yè)邏輯層的事,與用戶(hù)界面層無(wú)關(guān),假設(shè)有一天你因?yàn)槊糠N原因放棄了用集合來(lái)存儲(chǔ)數(shù)據(jù)的設(shè)計(jì),而改用數(shù)組或記錄集(Recordset)來(lái)存儲(chǔ),那你只需要修改GetFirst和GetNext兩個(gè)函數(shù),用戶(hù)界面層完全無(wú)需修改。
至此類(lèi)文件創(chuàng)建完畢,將工程文件存為 test.vbp,選File菜單下的Make test.dll選項(xiàng)將其編譯。
3、注冊(cè)動(dòng)態(tài)鏈接庫(kù)
啟動(dòng)Web Server 上的Microsoft Transaction Server (Start--Windows NT Optionpack4--Internet Information Server--Internet Service Manager),展開(kāi)Microsoft Transaction Server--Computer--My Computer--Package Installed,點(diǎn)鼠標(biāo)右鍵選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會(huì)發(fā)現(xiàn)DLL中有兩個(gè)類(lèi)Employee和Employees。至此DLL注冊(cè)完畢。
4、編寫(xiě)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中創(chuàng)建類(lèi)實(shí)例的語(yǔ)法set oEmployees=server.createobject("Test.Employees"),其中Test是DLL的名字,Employees是類(lèi)的名字; 當(dāng)然,如果一個(gè)函數(shù)的返回值是一個(gè)對(duì)象,類(lèi)似set oEmployee=oEmployees.GetFirst()這樣的語(yǔ)法也是可以的。
至此,一個(gè)完整的三層結(jié)構(gòu)的應(yīng)用程序已經(jīng)完成了,讓我們看以下,如果把"合格"的定義改為:只有成績(jī)進(jìn)入前100名才算合格,程序需要做那些修改。事實(shí)上,如果你的數(shù)據(jù)庫(kù)系統(tǒng)是SQL Server,你只需把SQL語(yǔ)句改為:
sql="select top 100 EMPLID from Employee order by Score desc" 就已經(jīng)可以了,即使為了跨數(shù)據(jù)庫(kù)系統(tǒng)的兼容性,我們也只需要對(duì)GetQualifiedList做如下修改:
sql="select EMPLID from Employee order by Score desc"
with rs
.open sql,conn,1,3
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
...
然后把DLL重新編譯,注冊(cè)就可以了,ASP程序完全不必修改。
四、一些說(shuō)明和注意事項(xiàng)
1、 由于這個(gè)例子比較簡(jiǎn)單,在Employee類(lèi)中可以沒(méi)有Create方法,而在Employees類(lèi)中將員工的所有信息(工號(hào),姓名,性別,成績(jī))都讀出來(lái)并將其賦給Employee對(duì)象對(duì)應(yīng)的屬性。但在實(shí)際應(yīng)用中,當(dāng)Employee對(duì)象的屬性增多,或表的數(shù)量增多,表之間關(guān)系變復(fù)雜時(shí),還是本文所示的方法更有效,代碼重用的機(jī)會(huì)更大。
2、當(dāng)DLL被修改后,在MTS中只能將其刪除后重新注冊(cè),因?yàn)槊看沃匦戮幾g后在注冊(cè)表中對(duì)象的ID值都將重新生成。
3、從ASP中調(diào)用帶參數(shù)的類(lèi)方法和函數(shù)時(shí),所有的變量參數(shù)一定要用相應(yīng)的類(lèi)型轉(zhuǎn)換函數(shù)轉(zhuǎn)換后再傳入,否則會(huì)引起類(lèi)型不匹配錯(cuò)誤,因?yàn)閂BScript中只有Variant類(lèi)型,它不能自動(dòng)轉(zhuǎn)換成其它類(lèi)型。例如,有如下的函數(shù)定義:
Public Function Fun1(p1 as string,p2 as integer) as integer
End Function
在ASP程序中應(yīng)如下調(diào)用:
<%
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
%>
而下面的兩種寫(xiě)法是錯(cuò)誤的:
<%
p1=obj.property1 ' Property1 is a string property
p2=obj.property2 'Property2 is an integer property
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
%>
這里第二種寫(xiě)法仍然是錯(cuò)誤的,即使經(jīng)過(guò)了類(lèi)型轉(zhuǎn)換,p1和p2仍然是Variant變量。在VBScript中,數(shù)據(jù)類(lèi)型和類(lèi)型轉(zhuǎn)換函數(shù)只在表達(dá)式運(yùn)算中起作用,變量只有Variant一種類(lèi)型。
結(jié)束語(yǔ)
以上對(duì)多層結(jié)構(gòu)的理論和實(shí)踐進(jìn)行了一番探討,希望能對(duì)您的開(kāi)發(fā)有所幫助。這里還有一個(gè)問(wèn)題,即類(lèi)和類(lèi)的成員該如何設(shè)計(jì)。這既涉及面向?qū)ο缶幊痰睦碚摚残枰欢ǖ膶?shí)踐經(jīng)驗(yàn)。請(qǐng)參考相關(guān)的OOP理論書(shū)籍并在實(shí)踐中不斷總結(jié),相信您一定能設(shè)計(jì)出自己的完美的多層結(jié)構(gòu)的應(yīng)用程序。