Private 該類(lèi)型的成員只能在聲明類(lèi)中被訪(fǎng)問(wèn)
Public 該類(lèi)型的成員可以被程序中的任何地方的代碼訪(fǎng)問(wèn)
Protected 該類(lèi)型的成員只能在聲明類(lèi)以及聲明類(lèi)的派生類(lèi)中被訪(fǎng)問(wèn)
Protected類(lèi)型的成員以及什么是“派生類(lèi)”等問(wèn)題我們留到以后再進(jìn)行討論,現在我們將注意力集中在前兩者。
Public類(lèi)型就是在上面例子中的那種類(lèi)型,這個(gè)很好理解。而Private類(lèi)型,根據表格中的簡(jiǎn)單解釋?zhuān)荒茉谠摮蓡T被聲明的那個(gè)類(lèi)(也就是該成員所屬的那個(gè)類(lèi)啦)中被訪(fǎng)問(wèn),越出這個(gè)界限,它就是不可見(jiàn)的。那么,Private類(lèi)型的成員將如何被使用呢?簡(jiǎn)單地說(shuō),就是通過(guò)一個(gè)Public類(lèi)的方法來(lái)訪(fǎng)問(wèn)它。
讓我們看一個(gè)新的例子:
type
TDate = class
private
Mouth,day,Year:Integer;
Public
procedure SetValue(m,d,y:Integer);
function LeapYear:Boolean;
function GetText:String;
end;
在這個(gè)類(lèi)中,Mouth,Day,Year這三個(gè)成員被聲明為Private成員,因此它們在類(lèi)以外的其它地方是不可訪(fǎng)問(wèn)的。也就是說(shuō),如果你使用
ADay.Year := 2000;
這樣的代碼,那么編譯器將會(huì )報錯。但是,我們可以照樣通過(guò)SetValue方法為它們賦值:
ADay.SetValue(1,1,2000);
這行代碼是合法的,因為SetValue本身是TDate類(lèi)的成員,而且它又是一個(gè)Public成員。而使用GetText方法則可以得到當前日期值(這也是得到當期日期值的唯一辦法)。
這樣的設置使得類(lèi)的一些成員被隱含起來(lái),用戶(hù)只能用一些專(zhuān)門(mén)的方法來(lái)使用它們。那些可以被外部代碼訪(fǎng)問(wèn)的成員稱(chēng)之為類(lèi)的接口。這樣做有什么好處呢?首先,這讓類(lèi)的作者可以檢測被賦值的內容。比如,用戶(hù)可能給一個(gè)對象賦予13月40日這樣的無(wú)效日期。而在隱含了一些成員之后,類(lèi)的作者可以在方法的代碼中檢測這些值是否有效,從而大大地減少了產(chǎn)生錯誤的機會(huì )。其次,使用規范的類(lèi),作者可以隨時(shí)修改類(lèi)內部的代碼,而使用該類(lèi)的代碼卻無(wú)需任何修改!這樣使得代碼的維護成了一件輕松的事件,特別是對于多人協(xié)作的大型軟件而言。
這就叫做數據的封裝(encapsulation)。這是OOP的第一個(gè)特征。一個(gè)優(yōu)秀的OOP程序員,應該在設計類(lèi)的時(shí)候,就確定將哪些重要的數據封裝起來(lái),并給出一個(gè)高效率的接口。
需要指出的一點(diǎn)是,表1中Private部分的論述對于“標準的”OOP語(yǔ)言(例如C++)是完全正確的,但對于Delphi有一個(gè)例外。在Delphi中,Private成員除了在聲明類(lèi)中可以訪(fǎng)問(wèn)外,在聲明類(lèi)所在的單元(.pas文件)中的任何地方都能被訪(fǎng)問(wèn),不論這些代碼與聲明類(lèi)的關(guān)系如何。嚴格來(lái)說(shuō),這是違反OOP的原則的,我不明白Borland為何要這么做(據說(shuō)是為了方便)。在關(guān)于Delphi的優(yōu)劣性的討論中,這是常被涉及的一個(gè)問(wèn)題。
1.2 繼承與派生
我們再來(lái)看一段代碼:
type
TNewDate = class(TDate)
Public
function GetTextNew:String;
end;
function GetText:String;
begin
return := inttostr(Mouth) + ‘:‘ + inttostr(Day) + ‘:‘ + inttostr(Year);
end;
可以看到,在class后面出現一個(gè)包含在括號中的類(lèi)名。這種語(yǔ)法表示新的類(lèi)繼承了一個(gè)舊的類(lèi)。繼承了原有類(lèi)的類(lèi)稱(chēng)之為派生類(lèi),也叫子類(lèi),被繼承的類(lèi)稱(chēng)之為基類(lèi),也叫父類(lèi)。
派生類(lèi)與基類(lèi)之間是什么關(guān)系呢?當派生類(lèi)繼承自一個(gè)基類(lèi)時(shí),它自動(dòng)具有基類(lèi)的所有數據、方法以及其他類(lèi)型,無(wú)須在派生類(lèi)中再做說(shuō)明。例如,可以象下面這段代碼這樣使用TNewDate類(lèi):
var
ADay: TNewDate;
begin
ADay := TNewDate.create;
ADay.SetValue(1,1,2000);
if ADay.LeapYear then
ShowMessage(‘閏年:‘ + Inttostr(ADay.year));
ADay.free;
end;
而且,派生類(lèi)還可以在基類(lèi)的基礎上加入自己的數據和方法??梢钥吹皆赥newDate類(lèi)中增加了一個(gè)新的方法GetTextNew。下面給出這個(gè)方法的實(shí)現部分:
function GetTextNew:String;
begin
return := GetText;
end;
然后調用它:
ADay.GetTextNew;
這個(gè)新的方法工作得很好。
為什么GetTextNew方法必須調用基類(lèi)中的GetText方法,而不能直接使用GetText方法中的那些代碼呢?原因是,Mouth,Day,Year這三個(gè)成員被聲明為Private成員,因此它們即使在派生類(lèi)中也是不能被訪(fǎng)問(wèn)的,所以必須調用基類(lèi)中的GetText方法,間接地使用它們。如果要直接使用它們的話(huà),可以將這三個(gè)成員的屬性從Private改為Protected。在表1中可以看到,Protected屬性的成員可以在聲明類(lèi)以及聲明類(lèi)的派生類(lèi)中被訪(fǎng)問(wèn),然而仍然不能被這兩種情況以外的其他代碼所訪(fǎng)問(wèn)?,F在我們終于可以理解了,這個(gè)特殊的屬性實(shí)際上提供了極大的方便:它使得類(lèi)的成員被封裝,避免了混亂,同時(shí)又能夠讓派生類(lèi)方便地使用它們。
?。ㄈ绻闶且粋€(gè)細心的人,你可能發(fā)現上面的話(huà)中間有一個(gè)小小的仳漏。當你真的在GetTextNew方法中訪(fǎng)問(wèn)了基類(lèi)的Private成員的話(huà),你可能會(huì )驚奇地發(fā)現程序也能夠編譯通過(guò)而且正常運行!其實(shí),這個(gè)問(wèn)題和OOP本身沒(méi)有關(guān)系。上面我已經(jīng)說(shuō)過(guò),在Delphi中,Private成員在聲明類(lèi)所在的單元文件中的任何地方都能被訪(fǎng)問(wèn),因此如果TNewDate類(lèi)和TDate類(lèi)在同一個(gè).pas文件中時(shí),這種情況就不足為怪了。)
怎么樣,是不是覺(jué)得非常奇妙?通過(guò)這種繼承的機制,類(lèi)不再僅僅是數據和方法的封裝,它提供了開(kāi)放性。你可以方便地繼承一個(gè)功能強大的類(lèi),然后添加進(jìn)自己需要的特性,同時(shí),你又不需要對基類(lèi)進(jìn)行任何的修改。相反,原作者對基類(lèi)的任何改動(dòng),都可以在你的新類(lèi)中立即反映出來(lái)。這非常符合代碼的重用要求。
這種繼承機制也非常符合現實(shí)世界中的情形??梢栽O想,一般意義上的“動(dòng)物”是一個(gè)類(lèi),具有自己的一些特征(成員);而“狗”是“動(dòng)物”的派生類(lèi),它具有動(dòng)物的所有特征,同時(shí)還具有自己獨有的特征(四條腿,汪汪叫,等等)。而“狗”這個(gè)類(lèi)可以繼續派生下去,例如“黑狗”“白狗”,它們除了保留狗的全部特征之外,還具有自己的特征(黑顏色,白顏色,等等)。而具體到一只活生生的狗,可以認為它就是“黑狗”或“白狗”(或其他什么狗)的一個(gè)實(shí)例(對象)。
OOP這種對現實(shí)世界的模擬不僅極大地簡(jiǎn)化了代碼的維護,而且使得整個(gè)編程思想產(chǎn)生了革命性的變化,較之模塊化編程有了飛躍的進(jìn)步。
如果你曾經(jīng)仔細閱讀過(guò)VCL的資料甚至它的源代碼,你就可以發(fā)現,整個(gè)VCL都是建立在這種強大的封裝-繼承的機制之上的。你可以看到一張詳細的VCL層次結構圖,就象是一個(gè)龐大的家譜,各種VCL構件通過(guò)層層繼承而產(chǎn)生。例如,一個(gè)簡(jiǎn)簡(jiǎn)單單的TForm類(lèi),就是許多次繼承之后的產(chǎn)物:
TObject - TPersistent - TConponent - TControl - TWinControl - TScrollingWinControl - TCustomForm - TForm
不但Delphi的VCL,Visual C++中的著(zhù)名的MFC(Microsoft Foundation Class,微軟基本類(lèi)庫),以及以前Borland C++中風(fēng)光一時(shí)的OWL(Object Window Library,對象窗口類(lèi)庫),都是建立在這種機制之上。所不同的是,對于前兩種語(yǔ)言,你要花上好幾個(gè)月的功夫去基本掌握那些繁復無(wú)比的類(lèi),才能寫(xiě)出比較有實(shí)用價(jià)值的程序,而在Delphi中,大部分的工作Delphi都已經(jīng)自動(dòng)幫你完成了。例如,每次你向程序中加入一個(gè)窗體時(shí),Delphi就自動(dòng)為你從TForm派生一個(gè)新類(lèi)(默認為T(mén)Form1),并且為這個(gè)新類(lèi)創(chuàng )造一個(gè)實(shí)例。你對這個(gè)窗體的改動(dòng)(添加構件和代碼之類(lèi)),無(wú)非是為這個(gè)派生類(lèi)加入一些新的特性而已;你再也用不著(zhù)自己去處理最大化、最小化、改變大小這一類(lèi)的情況,因為這些代碼都在基類(lèi)中被實(shí)現,而被派生類(lèi)所繼承了。這就是Delphi的偉大之處。當然,Delphi的VCL也絕不比MFC或OWL遜色(事實(shí)上它是由后者演變而來(lái))。
?。赡苡腥藭?huì )問(wèn)起VB的情況。VB不支持繼承,因此并沒(méi)有什么復雜的類(lèi)庫,它自己的控件也少得可憐,主要是使用ActiveX控件。)。
也許你已經(jīng)若有所悟,為你的發(fā)現而心癢難騷了吧。但是,我們要討論的東西當然不會(huì )僅僅這么簡(jiǎn)單。
在1.1部分(“數據封裝”),我們講到了“Create方法是每一個(gè)Class都具有隱含的方法”。其實(shí),這種說(shuō)法是不準確的。事實(shí)是,在Delphi中,所有的類(lèi)都默認繼承自一個(gè)最基礎的類(lèi)TOject,甚至在你并未指定繼承的類(lèi)名也是如此。Create方法是TObject類(lèi)具有的方法,因此理所當然,所有的類(lèi)都自動(dòng)獲得了Create方法,不管你是否實(shí)現過(guò)它。想想看就知道了:如果沒(méi)有Create方法的話(huà),怎樣建立一個(gè)對象呢?
你可能注意到了Create方法是一個(gè)特殊的方法。不錯,Create方法的確非常特殊,甚至于它的“頭銜”不再是function或procedure,而是Constructor(構造器)。你可以在VCL的源碼中見(jiàn)到這樣一些例子:
Constructor Create;
構造器不僅是一個(gè)Delphi關(guān)鍵字,而且是一個(gè)OOP方法學(xué)的名詞。與之相對應的,還有Destructor(毀壞器)。前者負責完成創(chuàng )建一個(gè)對象的工作,為它分配內存,后者負責釋放這個(gè)對象,回收它的內存。要注意的一點(diǎn)是,Constructor的名字一般是Create,但Destructor的名字卻不是Free,而是Destroy。例如:
Destructor Destroy;
那么,在以前的代碼,為什么又使用Free來(lái)釋放對象呢?二者的區別是,Destroy會(huì )直接釋放對象,而Free會(huì )事實(shí)檢查該對象是否存在,如果對象存在,或者對象不為nil,它才會(huì )調用Destroy。因此,程序中應該盡量使用free來(lái)釋放對象,這樣更加安全一些。(但要注意,free也不會(huì )自動(dòng)將對象置為nil,所以在調用free之后,最好是再手動(dòng)將對象置為nil。)
象對待一般的函數或過(guò)程那樣,也可以向構造器傳遞參數:
type
TDate = class
private
Mouth,day,Year:Integer;
Public
function LeapYear:Boolean;
function GetText:String;
Constructor Create(m,d,y:Integer);
end;
procedure TDate.Create(m,d,y):Integer;
begin
Mouth := m;
Day := d;
Year := y;
end;
調用它:
ADay: TDate;
begin
ADay := TDate.create(1,1,2000);
if ADay.LeapYear then
ShowMessage(‘閏年:‘ + Inttostr(ADay.year));
ADay.free;
end;
這樣,在Create方法里就完成了對數據的初始化,而無(wú)須再調用SetValue方法了。
接下來(lái),我們將要涉及到另一個(gè)重要的、也是很有趣的問(wèn)題:方法的虛擬與重載。
可能你已經(jīng)有點(diǎn)暈了吧……還是先看一個(gè)新的例子:
type
TMyClass = class
procedure One;virtual;
end;
type
TNewClass = class(TMyClass)
procedure One;override;
end;
procedure TMyclass.One;virtual;
begin
ShowMessage(‘調用了TMyclass的方法!‘);
end;
procedure TNewClass.One; override;
begin
Inherited;
ShowMessage(‘調用了TNewClass的方法!‘);
end;
可以看到,從TMyClass派生了一個(gè)新類(lèi)TNewClass。這兩個(gè)類(lèi)都聲明了一個(gè)名字相同的方法One。所不同的是,在TMyClass中,One方法后面多了一個(gè)Virtual關(guān)鍵字,表示這個(gè)方法是一個(gè)虛擬方法(Virtual Method)。而在TNewClass中,One方法后面多了一個(gè)Override關(guān)鍵字,表示該方法進(jìn)行了重載(Override)。重載技術(shù)能夠實(shí)現許多特殊的功能。
讓我們來(lái)仔細分析它們的實(shí)現部分。在TMyclass.One方法的實(shí)現部分,調用ShowMessage過(guò)程彈出一個(gè)對話(huà)框,說(shuō)明該方法已被調用;這里沒(méi)有任何特別的地方。在TNewClass.One方法中,出現了一條以前從未出現過(guò)的語(yǔ)句:
Inherited;
這個(gè)詞的中文意思是“繼承”。我們暫時(shí)不要去涉及到太過(guò)復雜的OOP概念,只要知道這條語(yǔ)句的功能就是了。它的功能是調用基類(lèi)中相當的虛擬方法中的代碼。例如,你如果使用以下代碼:
var
AObject: TNewClass;
begin
AObject := TNewClass.create;
AObject.One;
AObject.free;
end;
那么程序將彈出兩次對話(huà)框,第一次是調用TMyclass類(lèi)中的One方法,第二次才是TNewClass.One方法中的代碼。
重載技術(shù)使得我們不但可以在派生類(lèi)中添加基類(lèi)沒(méi)有的數據和方法,而且可以非常方便地繼承基類(lèi)中原有方法的代碼,只需要簡(jiǎn)單地加入Inherited就可以了。如果你不加入Inherited語(yǔ)句,那么基類(lèi)的相應方法將被新的方法覆蓋掉。但是必須注意,重載只有在基類(lèi)的方法被標志為Virtual時(shí)才能進(jìn)行,而且重載的方法必須具有和虛擬方法完全相同的參數類(lèi)型。
虛擬方法還有一種特例,即抽象方法:
procedure One;override;abstract;
在One方法后面,不但有override關(guān)鍵字,還多了一個(gè)abstract關(guān)鍵字(意為抽象)。這種方法稱(chēng)為抽象方法(在C++中稱(chēng)為純虛擬函數)。含有抽象方法的類(lèi)稱(chēng)為抽象類(lèi)。抽象方法的獨特之處在于,它只有聲明,而根本沒(méi)有實(shí)現部分,如果你企圖調用一個(gè)對象的抽象方法,你將得到一個(gè)異常。只有當這個(gè)類(lèi)的派生類(lèi)重載并實(shí)現了該方法之后,它才能夠被調用。(在C++中,甚至根本就不能建立一個(gè)抽象類(lèi)的實(shí)例。)
既然如此,那么這種抽象方法又有什么用呢?這個(gè)問(wèn)題我們將在接下來(lái)的“多態(tài)”部分進(jìn)行討論。
1.3 多態(tài)
多態(tài)相對來(lái)說(shuō)比較復雜一點(diǎn)。不過(guò)不要擔心,它的內容比較少,而且如果以前的知識掌握得比較穩固的話(huà),多態(tài)的概念是水到渠成的。
先來(lái)討論一下類(lèi)型的兼容性問(wèn)題。下面是一個(gè)例子:
type
TAnimal = Class
Procedure Voice;virtual;
...
end;
TDog = Class(TAnimal)
Procedure Voice;Override;
...
end;
implementation
Procedure TAnimal.Voice;virtual;
Begin
PlaySound(‘Anim.wav‘,0,snd_Async);
End;
Procedure TDog.Voice;virtual;
Begin
PlaySound(‘Dog.wav‘,0,snd_Async);
End;
TDog類(lèi)繼承了TAnimal類(lèi),并重載了其中的Voice方法。PlaySound是一個(gè)WIN API函數,可以播放指定的wav文件。(這個(gè)函數的定義在MMSystem.pas文件中可以找到。)
先看這段代碼:
var
MyAnimal1, MyAnimal2: TAnimal;
Begin
MyAnimal1 := TAnimal.Create;
MyAnimal2 := TDog.Create;
...
在實(shí)現部分的第一行中,建立了一個(gè)TAnimal類(lèi)型的對象,并將其賦予TAnimal類(lèi)型的變量MyAnimal1。這是很正常的事。但在第二行中,建立了一個(gè)TDog類(lèi)型的對象,并將其賦予了TAnimal類(lèi)型的變量MyAnimal2。這看上去令人吃驚,但這些代碼是完全合法的。
眾所周知,Pascal以及Object Pascal是一種類(lèi)型定義嚴格的語(yǔ)言,你不能將某個(gè)類(lèi)型的值賦予不同類(lèi)型的變量,例如將一個(gè)整型值賦予布爾型變量,將會(huì )導致出錯。但是,這個(gè)規則在涉及到OOP領(lǐng)域時(shí),出現了一個(gè)重要的例外,那就是:可以將一個(gè)子類(lèi)的值賦予一個(gè)父類(lèi)類(lèi)型的變量。但倒過(guò)來(lái)卻是不行的,一個(gè)父類(lèi)的值決不能賦予一個(gè)子類(lèi)類(lèi)型的變量。
如果將這個(gè)原則放到現實(shí)世界中,那就很容易理解了:“狗”繼承自“動(dòng)物”,因為狗也是一種動(dòng)物。所以可以將一個(gè)“狗”類(lèi)型的值賦予“動(dòng)物”類(lèi)型的變量,因為“狗”具有“動(dòng)物”的一切特征。但反過(guò)來(lái),“動(dòng)物”不具有“狗”的所有特征,因此反向賦值是不行的。
那么,這種兼容規則在編程中究竟有什么用處呢?
請注意下面這段代碼:
var
MyAnimal1, MyAnimal2: TAnimal;
Begin
MyAnimal1 := TAnimal.Create;
MyAnimal2 := TDog.Create;
MyAnimal1.Sound;
MyAnimal2.Sound;
...
MyAnimal1和MyAnimal2都是TAnimal的變量,而且都調用了Sound方法。但是,執行的結果是完全不同的:前者執行的是TAnimal.Voice的代碼,而后者執行的是TDog.Voice的代碼!其原因很簡(jiǎn)單,因為MyAnimal1被賦予了TAnimal類(lèi)型的對象,而MyAnimal2被賦予了TDog類(lèi)型的對象。也就是說(shuō),一個(gè)TAnimal類(lèi)型的變量,當它調用Sound方法時(shí),所執行的代碼是不確定的:可能執行TAnimal.Voice的代碼,也可能執行的是TDog.Voice的代碼,取決于它當時(shí)引用的是一個(gè)什么樣的對象。
再看:
MyAnimal1 := TAnimal.Create;
MyAnimal1.Sound;
MyAnimal1.free;
MyAnimal1 := TDog.Create;
MyAnimal1.Sound;
...
同一個(gè)變量MyAnimal1,在第一次調用Sound方法時(shí),執行的是TAnimal.Voice的代碼,在第二次時(shí)執行的是TDog.Voice的代碼。MyAnimal1.Sound這行代碼不需要變化,程序可以根據不同的情況賦予該變量不同的對象,從而使它執行不同的代碼。這就是多態(tài)的定義。
這個(gè)非常重要的特點(diǎn)大大地增加了代碼的可復用性。如前所述,只需要簡(jiǎn)單地寫(xiě)下一行代碼,就可以讓程序執行不同的功能,因為這個(gè)虛擬方法同TAnimal的任何派生類(lèi)都是兼容的,甚至連那些還沒(méi)有編寫(xiě)出來(lái)的類(lèi)也是一樣。而程序員并不需要了解這些派生類(lèi)的細節。利用多態(tài)性寫(xiě)出來(lái)代碼,還具有簡(jiǎn)潔和維護性好的特點(diǎn)。
現在我們可以回到本文的1.2節結尾處的問(wèn)題了。抽象方法本身不能夠做任何事情,必須在子類(lèi)中被重載并實(shí)現,才能夠完成有意義的工作。但抽象方法的存在,相當于為父類(lèi)留下了一個(gè)接口,當程序將一個(gè)子類(lèi)的對象賦予父類(lèi)的變量時(shí),父類(lèi)的變量就可以調用這個(gè)方法,當然此時(shí)它運行的是相應的子類(lèi)中重載該方法的代碼。如果沒(méi)有這個(gè)抽象方法,父類(lèi)的變量就不能調用它,因為它不能調用一個(gè)只在子類(lèi)中存在、而在父類(lèi)中不存在的方法!
關(guān)于OOP的介紹就到此這止。在以上這些篇幅里,介紹的只是OOP最基本的一些概念,讓讀者對OOP有一定的系統認識,也為下文的討論打好基礎 。更多、更深入的東西等待著(zhù)你自己去發(fā)掘。
本文已經(jīng)多次強調OOP的重要性,這里還要強調一次:對OOP的掌握程度,在某種意義上決定著(zhù)你對Delphi世界的理解能力。
2、數據庫
在相對枯燥的理論之后,我們終于要開(kāi)始接觸到一些比較激動(dòng)人心的實(shí)際應用了。
數據庫編程是Delphi最強大的優(yōu)勢之一,恐怕也很少有Delphi程序員沒(méi)有接觸過(guò)數據庫編程的。Delphi獨特的Data-Aware構件,讓很多初識Delphi的人為之目瞪口呆。不需要寫(xiě)任何代碼,在幾分鐘之內就可以做出一個(gè)相當精巧的數據庫程序,而且在開(kāi)發(fā)期就可以看到運行期的結果,這真是不可思議??!但是,Delphi強大無(wú)比的數據庫開(kāi)發(fā)能力,決不僅僅限于用幾個(gè)構件操縱一下DBF或是Access數據庫而已。你所看到只是冰山一角。讓我們仔細說(shuō)來(lái)。
數據庫雖然家族龐大,但一般來(lái)說(shuō)可以分為兩種:文件型數據庫和C/S型數據庫。下面分別討論。
2.1 文件型數據庫
所謂文件型數據庫,顧名思義,是基于文件的(file-based),數據被按照一定格式儲存在磁盤(pán)里,使用時(shí)由應用程序通過(guò)相應的驅動(dòng)程序甚至直接對數據文件進(jìn)行讀取 。也就是說(shuō),這種數據庫的訪(fǎng)問(wèn)方式是被動(dòng)式的,只要了解其文件格式,任何程序都可以直接讀取,這樣就使得它的安全性相當糟糕。同時(shí),在蓬勃興起的網(wǎng)絡(luò )應用,文件型數據庫更是難有用武之地:效率低下,不支持很多SQL命令,不支持視圖、觸發(fā)器、存儲過(guò)程等高級功能,等等。這些特點(diǎn)決定了它不適合大型的工程。
最為大家所熟悉的文件型數據庫可能就是DBF(DBase/Foxbase/Foxpro)數據庫,在DOS時(shí)代風(fēng)靡一時(shí),相信很多人都有過(guò)抱著(zhù)一本手冊苦背Foxbase命令的回憶吧!其特點(diǎn)是,每個(gè)Table或Index都是一個(gè)獨立的文件,使用相當簡(jiǎn)單,性能還可以,安全性非常的差,但應用非常廣泛(主要是DOS時(shí)代遺留下來(lái)的,哪個(gè)單位沒(méi)有兩個(gè)用這種東東編出來(lái)的老古董程序呢?)。它在今天還能占有一席之地,其主要原因之一是,正因為簡(jiǎn)單和使用廣泛,使得對它的訪(fǎng)問(wèn)是最容易的,甚至根本無(wú)需第三方的接口,就可直接對其進(jìn)行字節級的讀取 。
除此之外,還有著(zhù)名的Access數據庫。這是MS Office里的構件之一,和DBF數據庫不同,所有的文件都被整合在一個(gè).mdb文件中,這樣就避免了數據庫變大之后管理上帶來(lái)的麻煩。同時(shí)它還提供密碼保護功能,安全性比DBF數據庫要好很多。Access數據庫除了一般的文本數據之外,還擅長(cháng)于對多媒體數據的處理,在對聲音、圖像乃至基于OLE的對象進(jìn)行處理時(shí),令DBF數據庫望塵莫及。隨著(zhù)微軟戰略的步步勝利,Access數據庫也不斷發(fā)展,憑借著(zhù)優(yōu)秀的性能和與MS Office的無(wú)縫結合,早已超越DBase系列,成為現今最強大的文件型數據庫了。
Delphi中附帶的Paradox也是一種文件型數據庫。它是Inprise公司自己的產(chǎn)品。因此和Inprise的系列開(kāi)發(fā)工具配合得很不錯。它支持密碼保護,支持標準的SQL,性能也還不錯,但是應用就不那么廣泛了。和DBF數據庫一樣,它的每一個(gè)Table都是一個(gè)獨立的文件,因此也有同樣的管理問(wèn)題。
上文說(shuō)到可以對文件型數據庫直接讀取,但實(shí)際編程中很少有人這么做。因為再簡(jiǎn)單的數據庫其實(shí)也是相當復雜的,一步步分析它的格式,從底層實(shí)現所有的數據庫應用,如果都要程序員去寫(xiě)的話(huà),可能會(huì )把人累死。所以數據庫的開(kāi)發(fā)商將這些訪(fǎng)問(wèn)代碼封裝起來(lái),向程序員開(kāi)放,程序員只需要調用相應的接口就可以了。
以DBF為例,使用DBase/Foxbase/Foxpro系列開(kāi)發(fā)工具,可以用它自己的語(yǔ)法開(kāi)發(fā)出應用程序。其中對DBF文件的具體操作被封裝了。對于A(yíng)ccess數據庫,微軟公布了一個(gè)DAO(Database Access Object),由一系列的DLL文件組成,封裝了對.mdb文件的訪(fǎng)問(wèn)。使用VB的讀者可能對DAO比較熟悉,只要在VB中嵌入DAO對象,就可以非常方便地訪(fǎng)問(wèn)Access數據庫了。ODBC(Open DataBase Connection,開(kāi)放數據庫互連)也是一種封裝,用意在于向開(kāi)發(fā)人員提供一個(gè)統一的接口,通過(guò)這個(gè)接口可以訪(fǎng)問(wèn)任何支持ODBC的數據庫,只要該數據庫提供了相應的ODBC驅動(dòng)。從這一點(diǎn)上來(lái)說(shuō),ODBC是一種更加高級的封裝。目前幾乎所有的主流的數據庫都能被ODBC所支持。打開(kāi)你的Windows的控制面板,就可以看到ODBC的圖標。
用Delphi寫(xiě)數據庫程序的人免不了要同BDE打交道。BDE(Borland Dasebase Engine,Borland數據庫引擎)是一個(gè)和ODBC類(lèi)似的東西,Borland/Inprise本來(lái)企圖用它來(lái)統一數據庫接口。但后來(lái)Inprise在和微軟的戰爭中敗下陣來(lái)(ODBC是微軟搞出來(lái)的),它又不肯放棄BDE,而是將其捆綁在Delphi/C++ Builder系列開(kāi)發(fā)工具中,結果好象變成這些開(kāi)發(fā)工具的一種附屬品了。
用BDE開(kāi)發(fā)數據庫程序相當容易。許多Delphi教科書(shū)在寫(xiě)到數據庫開(kāi)發(fā)這一章時(shí),總是告訴你先在BDE中為某個(gè)DBF或Paradox數據庫設置一個(gè)別名,然后往窗體上放一個(gè)TTable構件,然后將其DatabaseName指向相應的別名……然后,這個(gè)數據庫中某個(gè)表的內容就在相應的Data-Aware構件中顯示出來(lái)了。但是它們具體是怎么工作的呢?
Delphi對數據庫進(jìn)行訪(fǎng)問(wèn)時(shí),事實(shí)上通過(guò)了很多層次的連接。如下圖:
圖1
DataAware構件-DataSource構件-DataSet構件-BDE-數據庫
從這個(gè)圖可以看出,BDE負責與具體的數據庫打交道,而Dataset構件與BDE相連,DataSource構件與Dataset構件相連,最后才連接到顯示具體數據的Data-Aware構件。在Delphi的構件面板上,Data Access頁(yè)面中的構件一般屬于DataSet構件,例如TTable、TQuery,只要指定它們的DatabaseName屬性,就可以將它們與某個(gè)數據庫建立連接。在Data Control頁(yè)面中的構件一般是Data-Aware構件,例如TDBGrid,TDBEdit,TDBImage。它們的作用看上去與一般的Delphi構件相似,不同之處在于,可以通過(guò)一個(gè)DataSource構件作為中介,與DataSet構件相連,并自動(dòng)顯示相應的數據。
用Delphi的數據庫構件建立一個(gè)應用程序是如此之方便,但是如果深入下去,會(huì )發(fā)現事情并不簡(jiǎn)單。你可以嘗試自己編寫(xiě)代碼,訪(fǎng)問(wèn)數據庫中字段,而不是通過(guò)Data-Aware構件由用戶(hù)來(lái)編輯。如何做到這一點(diǎn)呢?秘密在于Field構件。
可以說(shuō),Field構件是Delphi數據庫應用程序的基礎 。當打開(kāi)一個(gè)DataSet構件時(shí),相應的數據會(huì )被讀取,并儲存在TTable或TQuery構件的Fields屬性中。這個(gè)屬性被定義為Field數組。通過(guò)直接訪(fǎng)問(wèn)數組,可以使用它們,例如:
Table1.Fields[0].AsInteger;
這段代碼訪(fǎng)問(wèn)了Table1中當前記錄的第一個(gè)字段,該字段的類(lèi)型為Integer。
也可以通過(guò)使用FieldbyName屬性來(lái)使用它們:
Table1.FieldbyName(‘Last Name‘).AsString;
這段代碼訪(fǎng)問(wèn)了Table1中當前記錄的名為L(cháng)ast Name的字段,該字段的類(lèi)型為String。
事實(shí)上,Data-Aware構件就是通過(guò)訪(fǎng)問(wèn)DataSet構件的Fields屬性來(lái)使用數據的。弄明白了這一點(diǎn)之后,你自己也可以嘗試改寫(xiě)一個(gè)常規的顯示構件,使之具有Data-Aware的性質(zhì)。其實(shí),大多數使用Delphi的數據庫高手并不喜歡使用Data-Aware構件,因為Data-Aware構件遠不用常規的構件來(lái)得靈活。DataSet構件除了Fields屬性之外,還具有數目眾多的特殊屬性、方法和事件,足以應付從小型文本數據庫到大型網(wǎng)絡(luò )數據庫的所有應用。本文不擬一一討論它們,如果讀者能將它們的運用爛熟于心的話(huà),可以說(shuō)應付數據庫編程就不會(huì )有多大問(wèn)題了。
請將注意力再次集中到圖1。在圖1的最后一環(huán),可以看到BDE連接到了具體的數據庫。其實(shí),在這一環(huán)中,也是有幾個(gè)層次的。理論上來(lái)說(shuō),BDE可以連接任何類(lèi)型的數據庫。對于一些比較簡(jiǎn)單的數據庫,例如ASCII(純文本型的數據庫)、dBase以及Delphi自己的Paradox,BDE可以直接訪(fǎng)問(wèn)。另外它也可以通過(guò)一些相應的驅動(dòng),訪(fǎng)問(wèn)特定的數據庫,例如通過(guò)DAO訪(fǎng)問(wèn)Access數據庫。對于不能直接支持的數據庫,BDE還可以連接到ODBC,通過(guò)ODBC進(jìn)行訪(fǎng)問(wèn),雖然這樣效率比較低。
這種性質(zhì)決定了BDE是一個(gè)相當龐大的東西。使用了BDE的Delphi程序,必須有BDE才能工作,所以必須同BDE一起發(fā)布。這樣往往造成這樣一種情況:只有幾百K的應用程序,在將整個(gè)BDE加入之后,體積將近10M!這對于以輕薄短小為長(cháng)的文件型數據庫,簡(jiǎn)直是一個(gè)致命的弱點(diǎn)。而且由于BDE要兼容太多的數據庫,本身也有不穩定的毛病,往往出現令人頭疼的問(wèn)題。同時(shí),通過(guò)安裝程序安裝BDE驅動(dòng)和設置數據庫別名也是一件很麻煩的事情,這一切使得BDE在Delphi程序員中很不受歡迎。在網(wǎng)上的Delphi技術(shù)論壇里,經(jīng)??梢钥吹綄DE的一片咒罵之聲……那么,有什么辦法可以繞過(guò)BDE嗎?
有的。目前來(lái)說(shuō),至少有以下三種方法:
(1) 使用第三方構件。
Inprise自己也很早就意識到了BDE的問(wèn)題,雖然他們不肯放棄BDE,但是從Delphi3起,仍然對程序員提供了一個(gè)不錯的選擇:創(chuàng )建自定義的DataSet構件。Delphi的開(kāi)發(fā)者們把所有有關(guān)BDE的東西從TDataSet類(lèi)中移走,放入了新的TBDEDataSet類(lèi)(TBDEDataSet類(lèi)是TDataSet類(lèi)的子類(lèi))。TDataSet類(lèi)被重新構造,其核心功能被虛擬化。因此,你只需要從TDataSet類(lèi)派生一個(gè)自己的新類(lèi),并重載一些指定的虛擬方法(用以訪(fǎng)問(wèn)具體的數據庫),你就可以得到一個(gè)自己的DataSet構件。它與BDE完全無(wú)關(guān),但可以象Delphi自己的DataSet構件一樣被使用,例如,訪(fǎng)問(wèn)其Fields屬性,乃至與Delphi的Data-Aware構件一起工作!
于是出現了大量的第三方構件,它們可以訪(fǎng)問(wèn)某種特定的數據庫。下面是一些比較常見(jiàn)的訪(fǎng)問(wèn)文件型數據庫或ODBC的第三方構件:
表2
名稱(chēng) 支持的數據庫類(lèi)型
Diamond Access
Halcyon DBase/Foxpro
Apollo DBase/Foxpro
mODBC 任何ODBC數據庫
ODBC Express 任何ODBC數據庫
這些控件被廣泛使用,在國內,就作者所知,財智家庭理財軟件使用了Diamond,而“追捕”(一個(gè)顯示指定IP的地址位置的共享軟件)使用了Halcyon。在使用這些第三方構件之后,軟件終于可以“輕裝上陣”,再也不用為BDE頭疼了。
(2) 使用ADO。
在Delphi5中,Inprise終于提供了一個(gè)比較徹底的解決方法,那就是ADO構件。從原理上來(lái)說(shuō),ADO與上述的第三方構件并無(wú)多大區別,只是它是Inprise官方開(kāi)發(fā)的;同時(shí),它連接的不是某個(gè)具體的數據庫,而是微軟提供的ADO對象。
ADO(ActiveX Data Object,ActiveX數據對象)是微軟提出的新標準,從理論上來(lái),能夠支持任何類(lèi)型的數據庫(甚至包括流式數據)。微軟力圖將它樹(shù)為新的統一數據庫接口,吹噓了它的許多優(yōu)點(diǎn)。Inprise一直是微軟不共戴天的競爭對手,對微軟的標準嗤之以鼻(BDE即是一例),但是由于種種原因,Inprise終于承認了ADO。平心而論,用ADO來(lái)取代BDE的確是一個(gè)不錯的解決方案,而且在Delphi中使用ADO也相當方便。從形勢看,ADO應該是未來(lái)的方向。但是,ADO本身也是相當大的。
(3) 從最底層開(kāi)發(fā)一個(gè)完整的數據庫引擎。
這是最徹底的辦法。徹底拋棄Delphi的數據庫支持,從字節開(kāi)始,開(kāi)發(fā)自己的數據庫。這種方法有其好處:第一,不用考慮兼容性問(wèn)題,例如不用去考慮用戶(hù)的數據庫文件是Access 97格式還是Access 2000格式的;第二,可以在性能上達到最充分的優(yōu)化,因為不需要通過(guò)任何通用接口,而是直接對磁盤(pán)文件進(jìn)行操作,這對于一些對性能要求苛刻的程序是很有用的;第三,能夠最大限度地減少冗余代碼,因為這種數據庫往往是特定格式的,而且只需要執行一些特定的操作,訪(fǎng)問(wèn)代碼當然要比通用數據庫精簡(jiǎn)得多。但這種方法的負面問(wèn)題也顯而易見(jiàn),那就是龐大的工作量。再簡(jiǎn)單的數據庫也是相當復雜的,從最底層實(shí)現一個(gè)完整的數據庫引擎,往往需要幾千行代碼,以及耐心和經(jīng)驗。
雖然聽(tīng)起來(lái)有些極端,但這樣做的也不乏其人。著(zhù)名的Foxmail就是使用了自定義的數據庫格式來(lái)儲存信件、地址本等有關(guān)信息。另一個(gè)共享軟件“電子書(shū)庫”也使用了自定義的.srm格式。作者開(kāi)發(fā)的iCompanion(網(wǎng)絡(luò )伴侶)也是使用自定義格式來(lái)儲存網(wǎng)絡(luò )記錄的。
限于篇幅,這里就不再對具體的程序進(jìn)行詳細的分析了。要補充的一點(diǎn)是,作者曾使用Diamond開(kāi)發(fā)過(guò)Rich Explorer,這是一個(gè)專(zhuān)門(mén)用于瀏覽著(zhù)名的大富翁論壇的離線(xiàn)數據庫(Access格式)的閱讀器。在作者的主頁(yè)上,可以找到Rich Explorer的全部源代碼,它完整地展示了一個(gè)使用第三方構件訪(fǎng)問(wèn)特定數據庫的程序(沒(méi)有使用Data-Aware控件),代碼也比較簡(jiǎn)單,適合于初學(xué)者分析,有心的讀者不妨作為參考。
2.2 C/S型數據庫
C/S(Client/Server,客戶(hù)機/服務(wù)器)型數據庫是當前數據庫應用的主流。
與文件型數據庫不同的是,C/S型數據庫應用程序由兩個(gè)部分組成:服務(wù)器和客戶(hù)機。服務(wù)器指數據庫管理系統(Database Manage System,DBMS),用于描述、管理和維護數據庫的程序系統,是數據庫系統核心組成部分,對數據庫進(jìn)行統一的管理和控制??蛻?hù)機則將用戶(hù)的需求送交到服務(wù)器,再從服務(wù)器返回數據給用戶(hù)。
C/S型數據庫非常適合于網(wǎng)絡(luò )應用,可以同時(shí)被多個(gè)用戶(hù)所訪(fǎng)問(wèn),并賦予不同的用戶(hù)以不同的安全權限。C/S型數據庫支持的數據量一般比文件型數據庫大得多,還支持分布式的數據庫(即同一數據庫的數據庫位于多臺服務(wù)器上)。同時(shí),C/S型數據庫一般都能完善地支持SQL語(yǔ)言(所以也被稱(chēng)作SQL數據庫)。這些特性決定了C/S型數據庫適合于高端應用。
常見(jiàn)的C/S型數據庫有著(zhù)名的Oracle, Sybase, Informix, 微軟的Microsoft SQL
server, IEM的DB2,以及Delphi自帶的InterBase,等等。
C/S型數據庫涉及到非常多的高級特性,是Delphi中,也是整個(gè)計算機領(lǐng)域中最大的應用之一。由于本期附錄中已有專(zhuān)文討論,本文就不擬詳細介紹了。
隨著(zhù)技術(shù)的不斷更新,C/S型的結構也開(kāi)始逐漸被多層(Multi-Tiered)數據庫模型所取代。
上面說(shuō)到,C/S型數據庫程序由服務(wù)器和客戶(hù)機兩個(gè)部分組成,因此被稱(chēng)為雙層(two-tiered)模型。文件型數據庫程序則被稱(chēng)為單層(single-tiered)模型。單層模型是最原始的數據庫模型。后來(lái)程序員們將數據庫轉移到一個(gè)強大的中央服務(wù)器上,讓它為多個(gè)功能較弱的客戶(hù)機提供服務(wù),這樣雙層模型出現了。雙層模型在金融、電力、通信等領(lǐng)域被廣泛使用,極大地推動(dòng)了網(wǎng)絡(luò )數據庫的發(fā)展。但是,雙層模型也逐漸暴露出其不足的一面。在這種情況下,出現了三層模型:應用程序中的數據模塊部分被分離出來(lái),轉移到一個(gè)單獨的服務(wù)器上,成為獨立的一層。三層和三層以上的模型,統稱(chēng)為多層模型。
` 簡(jiǎn)言之,三層模型由以下三個(gè)層次組成:
客戶(hù)機-應用程序服務(wù)器-數據庫服務(wù)器
用戶(hù)的請求首先通過(guò)客戶(hù)機向應用程序服務(wù)器發(fā)出,應用程序服務(wù)器再向數據庫服務(wù)器發(fā)出具體的數據訪(fǎng)問(wèn)命令(一般是SQL),數據庫服務(wù)器返回的數據被應用程序服務(wù)器重新組織之后返回給客戶(hù)機。
可以看出,三層模型是雙層模型的擴展。目前我們無(wú)需了解三層模型的所有技術(shù)細節,以及它對于雙層模型的優(yōu)勢,只需要大致理解這個(gè)模型的結構就可以了。
B/S模型無(wú)疑是當前最為流行的多層數據庫模型之一。也許你已經(jīng)聽(tīng)說(shuō)過(guò)B/S這個(gè)名詞,它是Brower/Server(瀏覽器/服務(wù)器)的縮寫(xiě)。Brower是指IE/Netscape這樣的瀏覽器,Server包括數據庫服務(wù)器和應用程序服務(wù)器。用戶(hù)通過(guò)瀏覽器發(fā)出某個(gè)請求,通過(guò)應用程序服務(wù)器-數據庫服務(wù)器之間一系列復雜的操作之后,返回相應的Html頁(yè)面給瀏覽器。
是不是覺(jué)得十分熟悉?對了,其實(shí)這就是大家再熟悉不過(guò)的Internet上的WEB數據庫,當然它也可以用于局域網(wǎng)。它實(shí)際上可以說(shuō)是一種最常見(jiàn)的多層模型。
在對數據庫的發(fā)展進(jìn)行回顧之后,我們終于趕上了最流行的步伐。但是,也許有人在這里卻感到泄氣了。因為他聽(tīng)說(shuō)現在的WEB數據庫編程,是Perl、ASP、PHP、JAVA這些語(yǔ)言的天下。難道我們一直忠實(shí)追隨的Delphi,在面對當代最流行的Web數據庫的時(shí)候,竟然面臨淘汰的命運?
不,不是的。其實(shí)Delphi對Web數據庫的開(kāi)發(fā)提供了非常良好的支持,特別是依據強大的數據庫支持和可視化編視的特點(diǎn),它有著(zhù)其他語(yǔ)言不可比擬的優(yōu)勢。
下面的內容將集中于用Delphi開(kāi)發(fā)Web數據庫。
首先要從Web本身講起。平時(shí)我們?yōu)g覽的Web頁(yè)面,一般可以分為兩種。一種是靜態(tài)頁(yè)面,這種頁(yè)面是文本格式的html文件。但是,要響應用戶(hù)的不同需求,從而反饋給用戶(hù)不同的頁(yè)面,就必須使用動(dòng)態(tài)頁(yè)面技術(shù)了。例如,根據用戶(hù)輸入的名字,迅速在數據庫中查找到相應的數據,并動(dòng)態(tài)生成一個(gè)頁(yè)面返回給用戶(hù)。
怎樣實(shí)現動(dòng)態(tài)頁(yè)面技術(shù)呢?最早的一種方法是CGI(Common Gateway Interface,通用網(wǎng)關(guān)接口)。這種接口允許瀏覽器發(fā)送和接收數據,并且基于應用程序的命令行進(jìn)行輸入和輸出。當瀏覽器發(fā)出指向應用程序的請求時(shí),Web服務(wù)器會(huì )自動(dòng)啟動(dòng)該程序,并在瀏覽器和應用程序之間傳遞輸入和輸出的數據。實(shí)現CGI的語(yǔ)言有很多種,其中比較流行的是Perl。另外,還有一種特殊的WinCGI。WinCGI與普通CGI的區別是,它通過(guò)INI文件來(lái)代替命令行參數作為輸入輸出,這主要是為Visual Basic設計的,因為VB不能訪(fǎng)問(wèn)環(huán)境變量。Delphi對這兩種CGI都提供了很好的支持,可以編寫(xiě)出非常復雜的CGI程序。
第二種方法是使用Web服務(wù)器內置的API接口。用這種接口編寫(xiě)出來(lái)的Dll文件,被Web服務(wù)器裝載到自己的內存空間中,當服務(wù)器接收到相應的頁(yè)面請求時(shí),它將啟動(dòng)一個(gè)新的線(xiàn)程來(lái)執行Dll中的相應代碼。由于不需要執行外部的EXE文件,這種程序的速度非???。這種API主要有ISAPI(Internet Server API)和NSAPI(Netscape Server API),其中前者已經(jīng)成為事實(shí)上的標準。Delphi對這兩種API也提供了很好的支持。提得一提的是,Delphi的C/S版提供了一個(gè)通用的類(lèi)框架,消除了CGI、WinCGI和ISAPI之間的區別,這樣,我們可以輕松地將一個(gè)應用程序在這三者之間轉換。
還有一種是Web服務(wù)器內置的腳本語(yǔ)言,可以被簡(jiǎn)單地嵌入html文件,通過(guò)Web服務(wù)器的解釋執行來(lái)產(chǎn)生動(dòng)態(tài)頁(yè)面。著(zhù)名的ASP、PHP、JSP都屬于此類(lèi)。這看起來(lái)和Delphi沒(méi)有什么關(guān)系,但事實(shí)上,Delphi也能提供對ASP的強力支持!ASP的初學(xué)者可能會(huì )把注意力都集中在它的腳本語(yǔ)言上,其實(shí)ASP是由三部分組成的:腳本、組件和html。光是掌握腳本和ASP原有的組件特性是遠遠不夠的,只有自行開(kāi)發(fā)組件,才能實(shí)現真正復雜、高效的應用。Delphi就非常適合開(kāi)發(fā)這種組件。國內著(zhù)名大富翁論壇,就是基于Delphi+ASP的成功例子,同時(shí)它還公布了所有的源代碼,可供借鑒。
讓我們先建立一個(gè)程序。這個(gè)程序和以往的Delphi不同,要求刪除所有的窗體和多余的代碼,最后只剩下這么一段:
program CgiDate;
{$APPTYPE CONSOLE} //這行編譯器指令表示這是一個(gè)控制臺程序,不需要窗體,在終端窗口中輸出
uses
sysutils;
begin
writeln(‘HTTP/1.0 200 OK‘);
writeln(‘CONTENT-TYPE: TEXT/HTML‘);
writeln;
writeln(‘<html> <head>‘);
writeln(‘ <title>Time</title>‘);
writeln(‘</head><body>‘);
writeln(‘<h1>Time at this site</h1>‘);
writeln(‘<hr>‘);
writeln(‘<h3>‘);
writeln(formatdatatime(‘"Today is " dddd,mmmm d,yyyy,‘ + ‘"<br> and the time is "hh:mm:ss AM/PM‘,now));
writeln(‘<h3>‘);
writeln(‘<hr>‘);
writeln(‘</body></html>‘);
end;
編譯后,將該程序置于Web服務(wù)器下的有scripts權限的子目錄中,并用瀏覽器訪(fǎng)問(wèn)它,就可以得到一個(gè)關(guān)于當前時(shí)間和日期的頁(yè)面。
分析一下這段代碼。格式很簡(jiǎn)單,都是用writeln生成標準輸出。首先的兩行生成html的描述,中間空一行,接下來(lái)的代碼生成了一個(gè)完整的html文件。這個(gè)文件被返回給瀏覽器并顯示出來(lái)。與靜態(tài)頁(yè)面不同是,有一行html文本是通過(guò)formatdatatime函數動(dòng)態(tài)生成的,因此根據當前時(shí)間的不同會(huì )有不同的顯示。
這就是一個(gè)最簡(jiǎn)單的CGI程序。雖然簡(jiǎn)單,但已經(jīng)可以看出CGI程序的基本工作原理:通過(guò)標準輸入輸出產(chǎn)生動(dòng)態(tài)的html頁(yè)面。
在這個(gè)基礎之上,可以方便地實(shí)現對數據庫的訪(fǎng)問(wèn),并生成相應的html。
下面是一個(gè)例子:
var:
Table1:TTable;
Begin
Showheader; // Showheader過(guò)程生成html文件的頭部,代碼與上例相似
Table1 := TTable.create(nil);
Try
Table1.Databasename := ‘DBDEMOS‘;
Table1.tablename := ‘Employee.db‘;
Table1.indexname := ‘Byname‘;
Table1.open;
ShowTabindex; //顯示表中的列
Finally
Table1.close;
Table1.free;
End;
Writeln(‘</body></html>‘);
End;
這段代碼動(dòng)態(tài)建立了一個(gè)Table對象,并將它與DBDEMOS數據庫中的表Employee.db相連(當然這些必須先在BDE中設置)。ShowTabindex過(guò)程顯示了該表中的兩個(gè)列:
procedure ShowTabindex;
begin
table1.frist;
writeln(‘<ul>‘);
while not table1.eof do
begin
writeln(format(‘<li>s%s%‘,[table1.fieldbyname(‘FirstName‘).AsString, table1.fieldbyname(‘LastName‘).AsString]);
Table1.Next;
End;
Writeln(‘</ul>‘);
End;
ShowTabindex函數遍歷了整個(gè)表,并將所有的FirstName和LastName字段打印出來(lái)。
在此基礎之上,可以產(chǎn)生復雜的動(dòng)態(tài)頁(yè)面,實(shí)現對數據庫的各種操作。當然,實(shí)踐中的問(wèn)題決不僅僅這么簡(jiǎn)單,例如,讀取有關(guān)的環(huán)境變量,要使用特殊的GetEnvironmentVariable變量,生成相應的URL,需要相應的http知識,產(chǎn)生供用戶(hù)提交請求的頁(yè)面,需要了解html表單的格式……但是,本文的目的不在于探討技術(shù)細節,而是著(zhù)重于基本概念和原理的了解,因此不再詳述,請讀者自己深入學(xué)習。
上面已經(jīng)說(shuō)過(guò),在Delphi中可以輕松地將一個(gè)應用程序在CGI、WinCGI和ISAPI之間轉換。上面這兩個(gè)例子生成的是EXE文件,因此是CGI(或WinCGI,取決于使用環(huán)境變量還是INI文件)程序。如果要改成ISAPI方式,除了要編譯成DLL文件之外,輸入輸出的部分也需要進(jìn)行修改,實(shí)現三個(gè)特殊的函數:
GetExtensionversion
TerminateExtension
HttpExtensionProc
Web服務(wù)器在裝載DLL、卸載DLL和請求DLL時(shí)會(huì )分別調用這三個(gè)函數,程序可以通過(guò)它們與瀏覽器進(jìn)行交互。具體的實(shí)現,這里也不詳述了。
通過(guò)以上幾個(gè)專(zhuān)題的學(xué)習,相信讀者對Delphi的了解更深了一層。本文旨在幫助讀者理解一些相對比較高級的概念和原理,很多地方只是介紹了最基礎的東西,沒(méi)有展開(kāi)。如果深入下去,將會(huì )產(chǎn)生很多章龐大的內容。因此,讀者在入門(mén)之后,需要更多的自己的努力。
下面是作者推薦的一些資料:
書(shū)籍:
《Delphi5從入門(mén)到精通》,Marco Cantu著(zhù),電子工業(yè)出版社。在Delphi世界中,Marco Cantu作為書(shū)籍作者可說(shuō)是人人皆知,Delphi從入門(mén)到精通系列更是絕對的經(jīng)典。這本書(shū)雖然并不面向高端用戶(hù),但它全面地論述了Delphi編程中的各個(gè)方面,象組件開(kāi)發(fā)、多線(xiàn)程、圖象處理、報表打印 、I/O、數據庫、Internet應用等等,是亟待提高的初學(xué)者的不二之選 。
《Delphi4編程技術(shù)內幕》,Charlie Calvert著(zhù),機械工業(yè)出版社。作者是一位經(jīng)驗極其豐富的高級程序員,用深入淺出的語(yǔ)言,將Delphi中的許多復雜問(wèn)題一一道來(lái),令人豁然開(kāi)朗。豐富的內容和獨特的風(fēng)格讓這本書(shū)的讀者不但有技術(shù)上的收獲,而且有閱讀上的快樂(lè )。很多人吐血推薦的東東,高手案頭必備之物。
《Delphi3開(kāi)發(fā)使用手冊》,機械工業(yè)出版社。由Delphi的內部開(kāi)發(fā)人員所著(zhù),權威性可見(jiàn)一斑。雖然它的可讀性并不是很好,但其全面、深入、權威的介紹,使得本書(shū)可以當作Delphi的使用手冊來(lái)讀。哪怕在Delphi5已經(jīng)發(fā)布的今天,這本書(shū)仍然有其不可替代的位置。
網(wǎng)絡(luò )資源:
大富翁論壇(http://www.delphibbs.com/),國內最著(zhù)名的Delphi技術(shù)論壇,幾乎所有的國產(chǎn)Delphi高手都聚集在那里,代表著(zhù)中國大陸Delphi世界的最高水平。練功場(chǎng)上有大量公開(kāi)的程序源碼和編程資料,豐富實(shí)用的論壇離線(xiàn)數據庫更是每個(gè)Delphi程序員的必備。
深度歷險(http://www.vclxx.com),最大的中文Delphi控件下載中心,更新迅速,本文中提到的一些控件可在此下載。
無(wú)雙譜(http://onekey.yeah.net),作者的個(gè)人主頁(yè),本文中提到的一些程序源碼與控件可在此下載。
聯(lián)系客服