馮靈凱
文檔應用程序的應用非常廣泛,如一般公眾使用的Office辦公軟件套件、各種行業的專業的編輯器等。所以不斷有學者對這些應用的相關技術進行了研究,并取得了很多有意義的成果。傳統的方法要實現不同的文檔類型的應用程序,需要分別開發不同的操作行為接口,不僅工作量大,有重復冗余的工作,而且開發出來的接口很難保持一致,更難于實現接口重用。無法滿足同一個框架下的不同文檔操作行為必須統一的需求。
本文使用了一種基于模板方法[1]的文檔設計模式來定義通用文檔程序架構中操作行為。在保證對外暴露統一接口的同時,不同類型的文檔又可以有各自不同的隱式行為,同時增加代碼復用。
現在通用文檔一般分為兩大類:單文檔和多文檔。對應的框架相應地也可分為:單文檔程序框架、多文檔程序框架和混合文檔程序框架。對應的操作行為相應地也可分為:單文檔操作行為、多文檔操作行為和混合文檔操作行為。而目前對于文檔操作行為的國內外的研究現狀有二大特點:
(1) 針對單文檔操作行為的研究較多,面向多文檔操作行為的研究較少。
(2) 在同一個框架下針對只能使用一種文檔操作行為的框架研究得比較多(單文檔程序框架或多文檔程序框架),能同時使用兩種文檔操作的框架行為研究得較少(混合文檔程序框架)。
綜合國內外的研究現狀,當前關于通用文檔程序架構中操作行為的研究有以下四個發展方向:
(1) 針對統一資源管理下的(如:SVN管理下的)文檔操作行為的研究[2]。
由于涉及到文檔的共享或獨占式占用,在保持服務器端文件的一致性的前提下,提供多用戶、一致的交互操作體驗。這需要我們提供額外的技術來保證。例如可以從定義統一的資源管理接口上下手,在保持上層調用接口的一致性同時,又可以兼容底下來自不同資源管理者提供的文檔管理操作行為服務。
(2) 虛擬單文件下的文檔操作行為相統一的研究。
虛擬單文件,就是要求把應用要用到的所有文檔都歸檔為一個單獨文件,而這個文件類似于一個壓縮包文件形式存在于本地或服務器磁盤上。這樣做帶來的好處是:保證所有單文件的內部文檔結構一致,方便做加解密和版本管理,也提高了加解密和版本管理的效率。因為你只要對單文件進行加解密和版本管理就行了。但是要實現虛擬單文件,還需要很多技術來支撐。例如可以從虛擬文件壓縮[3],加解密算法和配置文件管理[4,5]等方面入手。把壓縮、加解密和讀寫配置都封裝為底層的服務組件。這樣上層應用的操作行為可以不用關心底層具體邏輯實現,更方便實現統一的操作行為。
(3) 對于不同版本文檔和不同版本的文檔程序,文檔操作行為相統一的研究。
就是說當文檔和文檔程序都有多個版本,且它們之間的版本沒有一一對應時,怎么保證文檔操作行為的統一?,F在當前很多應用對于版本不一致的處理,是根據水桶效應,取它們之間版本最低的那個版本的操作行為來處理的。這會導致:低版本的文檔把高版本的文檔程序中的最新功能給屏蔽了,或低版本的文檔程序把高版本的文檔回退到了低版本。沒有做到各盡其用。
(4) 對協同編輯操作行為的研究[4]。
模板方法[1],是GOF中的23種設計模式中的一種模式。模板方法:在一個方法中定義一個算法的骨架,而將一些實現步驟延遲到子類中。模板方法使得子類可以在不改變算法結構的情況下,重新定義算法中的某些步驟,如圖1所示:

圖1 GOF中的模板方法的類圖[1]
基于模板方法的文檔設計模式,就是在文檔應用框架中定義一簇通用文檔程序架構中操作行為的骨架。涉及到行為統一的邏輯處理,都會被放到框架中實現,而對于不同文檔操作行為的處理,延遲到對應文檔類中來實現。這樣使得單文檔操作行為和多文檔操作行為在UI層上是行為統一的。從而給用戶帶來一致的交互體驗。
通用文檔程序中的操作行為難以實現重用和擴展,根本原因是沒有把處理邏輯和處理程序有效分離。本文設計了一套基于模板方法的文檔設計模式,去幫助通用文檔程序的開發人員處理文檔程序的通用邏輯,規定通用文檔程序的操作行為,然后開發人員可在此基礎上實現自己處理邏輯,以此實現通用文檔程序架構中操作行為的通用性。
通用文檔程序架構中操作行為的核心算法(簡稱:通用文檔算法)是基于模板方法的文檔設計模式的一種具體實現,但不僅限于此。
該算法包括框架層核心算法、單文檔級核心算法、多文檔級核心算法。
考慮一個提供應用類(Application)和文檔類(Document)的應用框架。
應用類就是用來處理文檔管理操作的。由于通用文檔應用程序都會進行新建工程(NewProject)、打開工程(OpenProject)、保存工程(SaveProject)、關閉工程(CloseProject)操作,這些操作,都涉及到處理以外部形式存儲的文檔和內存中的文檔,而其中的關鍵邏輯是文檔的序列化和反序列化。為了保證行為統一,這邊使用了模板方法的思路。保證在做真正具體某個操作時,先處理(帶Pre的操作)一定出現在處理前,后處理(帶Post的操作)一定出現在處理之后。如果不使用通用文檔算法,人們就必須在每個子類中分別保證相互的行為統一。不幸的是,人們很容易忘記具體的行為順序。在你使用了通用文檔算法后,你只要在子類中實現要實現的接口,就能保證相互的行為統一。而且編譯器能保證你必須事先實現那些接口,不然程序是編譯不過的。
新建工程和打開工程都會調用關閉工程,這個也是為了保證行為統一。防止人們在新建工程和打開工程前忘記了關閉先前打開的工程,如圖2所示:

圖2 文檔管理類類圖(即應用類類圖)
內存文檔基類(簡稱文檔基類)是內存中的文檔對象的表示,必須是個抽象類。負責具體文檔的序列化操作(新建文檔OnNew、打開文檔OnOpen、保存文檔OnSave、關閉文檔 OnClose) 、文檔的邏輯操作(剪切記錄項 Cut、復制記錄項 Copy、粘貼記錄項 Paste)、文檔中某一記錄項的操作(新建記錄項NewItem、打開記錄項OpenItem、 保存記錄項 SaveItem、關閉記錄項 CloseItem)、對于文檔操作的 UI層提示操作(提示用戶是否保存當前記錄項的修改AskForItemSave、提示用戶為新的記錄項命名AskForName) 、更新UI操作(根據記錄項數據更新UI上控件UpdateUI、控制控件是否可被操作EnableUI等)、文檔操作中需要的輔助操作(得到某個記錄項GetItem、起一個不重復的記錄項名稱MakeName等)等的定義或實現。
如圖3所示:

圖3 文檔基類類圖
AskForItemSave、AskForName、UpdateUI、MakeName操作都使用了模板方法,對應的單文檔子類或多文檔子類則必須要實現 SaveItem、GetNameFromUI、ValidateName、OnUpdatUI、EnableUI操作。在內存文檔類中為了保證單文檔和多文檔的在數據層、邏輯層和UI層的操作行為統一,對于文檔路徑 m_strFilePath的用途也是有不同的含義和解釋的,這些在5.2和5.3中會進行闡述。同時為了使單文檔與多文檔的操作行為保持統一和方便做撤銷操作和檢測修改標志操作,需要一個臨時的記錄項m_item來表示當前操作的記錄項。
使用通用文檔算法構建的具體應用可以通過繼承應用類和文檔類來滿足特定的需求。
單文檔類是文檔基類的一個子類。在內存中所有記錄項是直接存放在單文檔對象中的,而所有記錄項序列化時都記錄在一個在外部形式存儲的文檔中。單文檔不需要一個記錄所有記錄項的索引文件。記錄項的索引信息和所有記錄項的數據信息,在內存中可以通過一個列表的形式進行記錄,m_items就實現了這個用途;在外部形式存儲的文檔中(其路徑為 m_strFilePath),則通過文檔的結構列表和具體的數據文本信息來表示。
文檔的邏輯操作、文檔中某一記錄項的操作、文檔操作中需要的輔助操作中的一些操作(如 GetItem),與多文檔不同,都是對內存對象的直接操作,不涉及對數據的序列化操作。
單文檔類的實現,如圖4所示:

圖4 單文檔類類圖
多文檔類也是文檔基類的一個子類。在內存中只有當前記錄項是直接存放在多文檔對象中的,而所有記錄項序列化時都分別記錄在各自在外部形式存儲的文檔中。其實準確地說沒有一次對所有記錄項進行序列化的操作,序列化操作一次只對單個記錄項進行。且多文檔必須要有一個記錄所有記錄項的索引文件(其路徑為m_strFilePath),其中記錄每一個記錄項的名稱和記錄項對應的文件名。由于這邊為了簡化算法,記錄項的名稱和記錄項對應的文件名我們用的是同一個。所以記錄項的索引信息在內存中,可以通過一個記錄項的名稱列表的形式進行記錄,m_strItemFilePaths就實現了這個用途;在外部形式存儲的文檔中(其文檔路徑m_strFilePath),則通過記錄名稱列表信息來表示。
在多文檔中,在OnOpen操作完成之后,在內存中只有記錄項的索引信息,不會有所有記錄項的數據信息,其實也用不著有,這個是我們有時會選擇使用多文檔而不使用單文檔的原因所在。所以想要得到某一非當前記錄項的數據時,必須進行一次序列化操作。而在單文檔中,由于所有記錄項的數據信息在OnOpen操作完成之后,已經全部序列化讀入內存,當想要得到某一非當前記錄項的數據時,不用再進行序列化操作了。
文檔的邏輯操作、文檔中某一記錄項的操作、文檔操作中需要的輔助操作中的一些操作(如GetItem),與單文檔不同,是對在外部形式存儲的文檔進行操作,涉及對數據的序列化操作。但是由于多文檔類和單文檔類的基類中(文檔基類)使用了模板方法,就會給用戶帶來一致的操作體驗。
多文檔類的實現,如圖5所示:

圖5 多文檔類類圖
部分關鍵操作的偽代碼實現,已經在圖2、圖3、圖4、圖5中給出。
現已經在盛大游戲《零世界》的專業版編輯器中得到運用,且取得不錯的效果。在該編輯器中:
對于每個記錄項數據多而復雜的模塊,采用多文檔操作行為。如:場景模塊、資源模塊等。
對于每個記錄項數據少而簡單的模塊,采用單文檔操作行為。如:任務模塊、怪物模塊、主角模塊、物品模塊等。
用戶在使用編輯器時,完全不會察覺各個模塊之間操作有何不同。
其實這邊給出的通用文檔算法十分通用,幾乎可以用在任何一個文檔程序中。說不定,你寫過的文檔程序就已經部分用上了這個算法。
本文設計了通用文檔算法,對于不同應用類型的文檔程序架構中操作行為進行了提煉,解決了操作行為統一的難題。只需要按照已定義的接口編寫對應的實現即可,開發效率會有很大提高,而且具有開發簡單、可靠性高、重用性強、可讀性好、容易操作等優點。若有新類型的文檔處理需求,可以通過添加新的單文檔或多文檔子類來實現;若有新的操作行為需求,可以通過在文檔基類添加新的操作行為接口,并在單文檔或多文檔類中實現之來解決。這對于解決通用文檔程序架構中操作行為的一致處理,有可能是一個比較合理的解決方案。
由此可見,研究通用文檔程序架構中操作行為,尤其是針對復雜龐大的編輯器,從文檔底層的數據操作、文檔的邏輯操作、文檔的UI層操作3個層面統籌考慮操作行為,具有非常重要的理論與現實意義。
當然通用文檔算法還有很多地方需要改進,讀者可以從以下幾個方面進行深入研究,為文檔程序架構中操作行為架構帶來更好的、更全面的應用解決方案:
(1) 針對統一資源管理下的(如:SVN管理下的)文檔操作行為的研究。
(2) 虛擬單文件下的單文檔和多文檔操作行為相統一的研究。
(3) 針對協同編輯操作行為的研究。
(4) 剪切、復制、粘貼支持一次操作多個記錄項。
(5) 多文檔中存放記錄項數據的文件名稱可以與記錄項名稱不同。
[1]Erich Gamma, Richard Helm, Ralph Johnson, John Lissides. Design Patterns:Elements of Reusable Object-Oriented software[C]. USA:Addison-Wesley Professional, 1994: 346-352.
[2]孫尚輝, 曹寶香, 王廷蔚. 擴展RBAC模型在文檔管理中的應用[J].. 計算機技術與發展, 2007, (3).
[3]劉麗偉, 鄧春健. 多文件壓縮傳輸及解壓縮的方法[J].武漢理工大學學報(交通科學與工程版), 2009, (6) .
[4]陳麗. 協同編輯系統中并發控制的研究與實現.[j]網絡出版年期, 2009, (7).
[5]崔明煜, 劉麗蘭, 宋娜, 孫海洋. 協同設計中基于標記文件的版本管理[J].. 機械工程師, 2007, (10).