1 引言
傳統報表系統,通常是針對某個商業領域使用,其使用的報表格式往往在設計時由設計人員已經定做成模版的形式存儲在模版庫中,用戶使用時直接從模版庫讀取,處理方式也僅限于該領域內;雖然在一定程度上該解決方法帶來了一定管理上的便宜,可對于現代企業用戶來說,報表格式單一已經局限了報表只能作為記賬簿來使用,使得報表的可塑性差,后期維護艱難,難以適應生產過程的多樣性和變化性,無法滿足大型企業不斷擴充的適應性、智能型的要求,特別是當業務領域變化時,原有的報表系統往往很難支持新業務數據的管理分析,要進行大量的重新開發工作。
在本通用報表的設計中,將報表框架與報表數據分離開,用戶即能根據自己的需要隨意繪制表格,又能按照業務要求自主的選擇數據來源。一旦框架和數據來源定義完畢,系統可以自動生成所需要的報表。為了經濟的達到方便使用的目的,必須吸收先進的軟件開發思想,采用優秀的軟件開發方法以提高軟件質量和軟件的重用性,其中提高軟件的重用性是減少開發成本的關鍵。
本文主要介紹MVC設計模式在通用報表系統開發中的應用,給出了具體問題相應的解決辦法,提高了軟件的通用性和擴展性。
2 設計模式
設計模式是設計面向對象軟件的過程中記錄的知識和經驗,用一系列類結構和對象來具體描述其含義。設計模式的目的就是復用這些面向對象設計的解決方案,根據具體應用完成具體的設計以及便于這些抽象解決方案的積累和交流。與不使用設計模式的軟件系統相比,一個大量使用設計模式的軟件系統的對象建模更加合理,對象間的耦合度更小,效率、可靠性、可升級性、并發性、平行性和分布性更高,更能獲得高層次的設計復用和代碼復用。
設計模式概念最先來自于城市建筑專家對建筑模式的定義“每一個模式描述了在人們周圍不斷反復發生的問題,以及該問題的解決方案的核心。這樣,你就能一次又一次的使用該方案而不必做重復勞動”。這種建筑上的模式思想在面向對象的設計模式中同樣適用,模式的核心就在于提供了相關問題的解決方案。設計模式確定了所包含的類和實例,它們的角色、協作方式以及職責分配。它通過刻畫部件靜態和動態結構及其之間的合作關系,成功地應用于解決商業數據處理、電子通信、圖形用戶界面、數據庫、分布式通信軟件等軟件構造中的問題。
一般而言,設計模式有4個要素:①模式名稱:用來描述問題、解決方案和效果。②問題:描述可以在什么時候使用設計模式。③解決方案:描述了設計模式的組成部分,它們之間的相互關系及各自的職責和協作方式。④效果:描述了模式應用的效果及使用模式應該權衡的問題。一個設計模式命名抽象確定了一個通用設計結構的主要方面,這些設計結構能用來構造可重用的面向對象設計。
我們在報表系統中主要使用了模型-視圖-控制器設計模式(MVC)、觀察者(Observer)、適配器模式(Adapter)以及橋接(Bridge)這幾種設計模式。
3 設計模式的應用
3.1 模型-視圖-控制器(MVC)
報表系統中為了方便用戶對數據的分析和使用,同一業務數據常常需要多種視圖呈現,即一個表格對象和一個柱狀圖對象可使用不同的表示形式描述同一個應用數據對象的消息。表格對象和柱狀對象并不知道對方的存在,這樣使用戶可以根據需要單獨復用表格或柱狀圖;當用戶改變比表格中的信息時,柱狀圖能立即反映這一變化,這一行為意味著表格和柱狀圖都依賴于數據對象。早期的圖形化程序設計常常圍繞著事件驅動的用戶界面來組織,這樣的直接后果就是數據處理、程序功能與顯示代碼完全糾結在一起。大型的圖形化程序中一個數據通常對應多種表示與處理方式,把特定界面綁定到應用程序上嚴重降低了程序的靈活性,使得一個很小的改動也牽扯到大量的代碼,增加了程序開發與維護的工作量。20世紀70年代,MVC模式在small talk 80的GUI設計中被提出,并且描述了不同部分的對象之間的通信方式,使它們不必卷入彼此的數據模型開發方法中,使程序結構變得清晰而靈活。
MVC模式包括三個部分:模型(Model)、視圖(View)和控制器(Controller),分別對應于內部數據、數據表示和輸入輸出控制部分。模型是與問題相關數據的邏輯抽象,代表對象的內在屬性,是整個模型的核心。它采用面向對象的方法,將問題領域中的對象抽象為應用程序對象,在這些抽象的對象中封裝了對象的屬性和這些對象所隱含的邏輯。視圖是模型的外在表現,一個模型可以對應一個或者多個視圖,如圖形用戶界面視圖、命令行視圖、API視圖;或按使用者分類:新用戶視圖、熟練用戶視圖等。視圖具有與外界交互的功能,是應用系統與外界的接口:一方面它為外界提供輸入手段,并觸發應用邏輯運行;另一方面,它又將邏輯運行的結果以某種形式顯示給外界。控制器是模型與視圖的聯系紐帶,控制器提取通過視圖傳輸進來的外部信息,并將用戶與View的交互轉換為基于應用程序行為的標準業務事件,再將標準業務事件解析為Model應執行的動作(包括激活業務邏輯或改變Model的狀態)。同時,模型的更新與修改也將通過控制器來通知視圖,從而保持各個視圖與模型的一致性。
實現MVC模式時面對的主要問題是Model和View的關系,在設計模式中的Observer模式很好的描述了如何建立這種關系。這一模式中關鍵的對象是目標(subject)和觀察者(observer)。一個目標可以有多個依賴它的觀察者;一旦目標發生變化,所有依賴它的觀察者都得到通知,并做出響應,即每個觀察者都將查詢目標進行更新,以保證和目標的狀態同步。這種模式允許我們獨立的改變目標和觀察者;用戶可以單獨復用目標對象而無需同時復用其觀察者,反之亦然。這種模式可以在不改動目標和其他觀察者的前提下增加觀察者。
在報表系統中,目標即為業務數據,觀察者定義為在用戶界面上的顯示視圖。顯示視圖是由其相關的業務數據決定,當業務數據發生變化時,視圖也將發生變化。同一業務數據會有多種顯示視圖;相同類型的視圖也可以表達不同的業務數據。而且可以根據需要在任意時刻增加和刪除顯示視圖,大大提高了報表系統的通用性。
當報表系統業務數據變化時,它要通知依賴它的所有視圖發生相應的變化,這樣就需要在業務數據對象里記錄依賴它的視圖,或是增加一個關聯查找機制。這兩種方法在數據和視圖間依賴關系比較少時可以高效的解決問題,但當它們之間的關系特別復雜時,我們就需要一個專門的對象來維護這些關系,這里我們稱之為更改管理器(ChangeManager),它的目的是盡量減少視圖反映數據變化所需的工作量。例如,如果一個操作涉及到對幾個相互依賴的目標進行改動,就必須保證僅在所有的目標都已經更改完畢后,才一次性的通知它們的觀察者,而不是每個目標都通知觀察者。另外,觀察者并不是對所有的事件都感興趣,可以擴展目標的注冊接口,讓個觀察者注冊為僅對特定事件感興趣,以提高更新的效率。當一個事件發生時,目標僅通知那些已注冊為對該事件感興趣的觀察者。
Observer模式在報表系統中的層次結構如圖1所示,其中DataEntry為業務數據抽象,它的具體實現由ConcreteDataEntry來完成;UserView是顯示視圖的抽象,具體使用的視圖由ConcreteUserView實現;更改管理器(ChangeManage)的實現有兩種:SimpleChangeManage和DAGChangeManage ,它們分別管理單一目標的目標—觀察者關系和多目標相互關聯的目標—觀察者關系。
圖1 報表系統中Observer模式層次結構圖
其中業務數據的類定義如下:
class DataEntry{
public :
virtual ~DataEntry();
virtual void Attach(UserView *pView ); //增加視圖
virtual void Detach(UserView *pView); //刪除視圖
virtual void Notify(UserView *pSender); //通知
protected:
DataEntry ();
private:
List<UserView*> *UserViewList; //記錄視圖
}
顯示視圖的定義為:
class UserView{
public:
DataEntry getDataEntry(); /*獲取業務數據實體的狀態,相當于模式中的GetState()和 SetState()*/
virtual void Notify (UserView *pView);
virtual void OnDraw(UserView *pView);
//設計為虛函數以供重載
protected:
List<DataEntry*> *DataEntryList;
//記錄業務數據實體
}
在這里簡要說明一下Notify()的實現:
void Notify:: DataEntry (UserView *pSender){
ASSERT(pSender==NULL||*UserViewList.IsEmpty()); //判斷視圖列表是否為空
POSITION pos = GetFirstViewPosition ();
While (pos! = NULL)
{UserView* pView = GetNextView (pos);
ASSERT_VAILD (pView);
if (pView != pUserView)
pView->Notity ();}
3.2 適配器模式(Adapter)
在許多大型企業及某些行業的局域網或廣域網內,由于歷史和技術發展的原因存在著多種數據庫同時在運行的情況,比如某大型企業、某地區的電力單位或電信公司等,可能正同時在使用著多種數據庫(Oracle, DB2, SQL Server, Sybase 或Informix等)。在這樣多的數據庫并存的環境下,要求能任意訪問到這些數據庫,實現多種數據庫間的數據轉化、資源共享、數據一致性和完整性成為系統開發和應用中一個尤為突出的問題。
構建通用的數據庫訪問主要是實現對數據源訪問的底層操作的封裝,而僅僅給出數據讀取對象或數據集對象等供商業邏輯層調用,因此采用Adapter模式,根據不同的數據提供者產生相應的數據庫連接、數據庫命令等數據庫對象來實現對低層操作的封裝,通過暴露執行數據集對象等上層操作以供其他邏輯層調用。
對數據庫的訪問基礎是基于結構化查詢語言(SQL),在具體對數據庫的訪問中,是通過SQL語句來實現的。采用這種設計模式對數據庫進行訪問的方法是一種通用訪問技術,即應用程序可用相同的源代碼訪問不同類型的數據庫,如Sybase, Oracle等。
Adapter模式是將一個類的接口轉換成客戶希望的另外一個借口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些類可以一起工作,它又稱為“Wrapper”包裝器。當要使用一個已經存在的類,而它的接口不符合需要,或是創建一個可以復用的類,該類可以與其它不相關的類或不可預見的類協同工作。
Adapter模式的基本結構圖如圖2所示:
圖2 Adapter模式的基本結構圖
Adapter模式應用在報表系統中,模式中Target即為應用程序中數據訪問類,它負責進行數據庫的連接、進行數據庫操作等等;模式中的Adaptee為實際存在的各種數據庫;在進行系統實現時的主要工作是對不同的數據庫編寫相應的數據源驅動程序,即編寫Adapter。
應用程序對數據庫的訪問主要是選擇一個數據源并連接它、提交SQL語句以及檢索結果,除此以外還可以確定并調整驅動程序的性能、瀏覽數據庫編目等等;應用程序中數據訪問類通過數據源驅動程序訪問不同數據資源中的數據,每個不同的數據資源類型由一個數據源驅動程序支持。
數據源驅動程序是處理數據訪問類對象的函數調用,提交SQL請求到一個指定的數據元,并把結果返回到應用程序;如果有必要,數據源驅動程序修改一個應用程序請求,以使請求與相關的DBMS支持的語法一致。每個驅動程序都針對特定的DBMS;例如,一個Oracle驅動程序不能直接訪問Informix DBMS中的數據。數據源驅動程序展示基礎DBMS的能力,他們不能實現DBMS不支持的能力。它進行數據源連接、檢查應用程序中的函數錯誤、初始化事務和把SQL語句提交給執行的數據源。數據源驅動程序必須把應用程序的 SQL修改成針對相應DBMS的SQL,并把數據發送到數據源,或從數據源檢索數據,包括根據應用程序的指定來轉換數據類型,最后斷開與數據源的連接。
當數據源驅動程序的數量增加,并且處理的事物逐漸復雜時,僅僅用應用程序的數據訪問類來進行驅動程序的管理已大大增加了系統的負擔,我們可以提供一個驅動程序管理器來管理數據源驅動程序。此時應用程序是被連接到驅動程序管理器,而不是驅動程序。它使用應用程序傳遞的連接句柄搜索目標驅動程序中的函數地址,并通過地址調用那個函數。驅動程序管理器多數只是把函數調用從應用程序傳送給正確的驅動程序。驅動程序管理器最終的作用是加載和卸載數據源驅動程序,應用程序只加載和卸載驅動程序管理器。當它要使用一個特殊的驅動程序時,它調用驅動程序管理器中的連接函數,并指明一個特殊數據源或驅動程序名。使用該名稱,驅動程序管理器為驅動程序文件名查詢數據源信息,比如SQLSRVR.DLL,然后它加載驅動程序,保存驅動程序中每個函數的地址,并調用驅動程序中的連接函數,然后初始化它自己,并連接到數據源。當應用程序使用驅動程序做完工作后,它調用驅動程序管理器中的SQLDisconnect。驅動程序管理其中調用驅動程序中的此函數,斷開與數據源的連接。然而,驅動程序管理器重新連接它時,把驅動程序保留在內存中。只有當應用程序釋放驅動程序使用的連接,或者使用不同的驅動程序連接,并且沒有器它連接使用此驅動程序時,它才卸載驅動程序。
3.3 橋接模式(Bridge)
Bridge設計模式時對象結構模式的一種,它將抽象部分與實現部分分離,使它們能夠獨立實現。當在一個程序中,某一個抽象可以由很多實現方法的時候,我們通常是使用面向對象中繼承的方法來實現并協調這些方法。但是這種繼承機制有以下不足的地方,而使用Bridge設計模式都能使這些不足得到很好地解決。
繼承機制使客戶代碼在實現功能的時候涉及到特定的相應平臺,與平臺產生相關性,對代碼的移植產生很大的困難,但是Bridge設計模式將抽象和實現部分放在獨立的不同層次的類結構中,將抽象中與系統平臺相關部分分離開來,同時也降低了實現部分對編譯的依賴性,當改變一個實現類時,并不需要重新編譯抽象部分和它的客戶程序。
我們的報表系統是一個通用系統,我們希望這個系統能夠支持多窗口系統。雖然不同的窗口系統有不兼容的程序設計接口,但是所有的窗口系統總的來說還是在做同一件事情,我們可以對不同的窗口系統做一個統一的抽象,在對各窗口系統的實現做一些調整,使之符合公共接口。
我們首先定義一個Windows抽象類,它封裝了需要各窗口系統都要做的一些事情,并且能跨越不同的窗口系統實現。在這個抽象類提供了支持大多數窗口系統的方便接口,其具體的子類支持用戶用到的不同種類的窗口,而對不同窗口系統的實現則由WindowImp類層次隱藏。WindowImp是一個封裝了窗口系統相關代碼的對象的抽象類,為了使報表系統運行于一個特定的窗口系統,我們用該子系統的一個WindowImp子類設置Window對象。這樣,避免了對窗口系統的直接依賴,這樣可以讓Windows類保持相對較小而且較穩定,同時還能方便的擴展實現層次結構以支持新的窗口系統。
Window和WindowImp層次結構之間的關系有下圖所示,其中Windows_X即為可以擴展的窗口系統接口。
圖3 Window和WindowImp層次結構
4 結語
應用設計模式可以使看似復雜的系統設計和實現簡單化,設計出來的系統具有靈活、健壯和可復用性強等特點,而且還可以方便開發人員的溝通和交流,保證軟件開發文檔的準確性和易讀性,便于代碼開發工作。在報表系統中引入設計模式,不僅加深了對面向對象思想的認識,而且使得系統的設計間接明了,提高了軟件系統的可維護性和伸縮性。
文章就設計模式在通用報表系統中的使用進行了一定的研究和探討。主要就通用報表系統中幾個關鍵的通用問題給出了相關的設計模式解決方案。該通用報表的設計為解決集團性企業、政府統計的報表匯總分析、預算編制、合并報表的解決方案;它還可以引入到分銷管理中,以承擔DRP系統中臨時性、非結構化數據的統計任務;根據統計管理對象的不同,它還能成為網上人事統計、資產管理系統的二次開發平臺。