這次,我將緊隨設計模式第二章的步伐,來完成一個文本編輯器Lexi的實現,大部分內容都會原樣的copy書中的東西,最后我將實現一個完整的文本編輯器,並且附上完整的源代碼,好了,讓我們開始吧。
一.設計問題
- 文檔結構 對文檔內部表示的選擇幾乎影響到Lexi設計的每個方面。所有的編輯、格式安排、顯示和問題吧分析都設計到這種表示。我們怎樣組織這個信息會影響到應用的其他方面。
- 格式化 Lexi是怎樣將文本和圖形安排在行和列上的?哪些對象負責執行不同的格式策略?這些策略又是怎樣和內部表述相互作用的?
- 修飾用戶界面 Lexi的用戶界面包括滾動條、邊界和用來修飾WYSIWYG文檔界面的陰影。這些修飾有可能隨着Lexi用戶界面的演化而發生變化。因此,在不影響應用其他方面的情況下,能自由增加和去除這些修飾就十分重要了。
- 支持多種視感(look-and-feel)標准 Lexi應不需做較大修改就能適應不同的視感標准,如Motif何Presentation Manager等
- 支持多種窗口系統 不同的視感標准通常是在不同的窗口系統中實現的。Lexi的設計應盡可能的對立與窗口系統
- 用戶操作 用戶通過不同的用戶界面控制Lexi,包括按鍵和下拉菜單。這些界面對應的功能分散在整個應用對象中。這里的難點在於提供一個統一的機制,既可以訪問這些分散的功能,又可以對操作進行撤銷。
- 拼寫檢查和連字符 Lexi是怎樣支持想檢查拼音錯誤和決定連字符的連字典這樣的分析操作的?當我們不得不添加一個新的分析操作時,我們怎樣盡量少修改相關的類。
二.文檔結構
內部表示應該支持如下幾點:
- 保持文檔的物理結構。即將文本和圖形安排到行、列、表等
- 可視化生成和顯示文檔
- 根據顯示位置來影射文檔內部表示的元素。這可以是Lexi根據用戶在可視化表示中所點擊的某個東西來決定用戶所引用的文檔元素
限制條件:
- 我們應該一致對待文本和圖形
- 我們的實現不應該過分強調內部表示中單個元素和元素組之間的差別。Lexi應該能夠一致對待簡單元素和組合元素,這樣就允許任意復雜的文檔。
遞歸組合:
層次結構信息的表述通常是通過一種被稱為遞歸組合(Recursive Composition)的技術來實現的。遞歸組合可以由簡單的元素逐漸建立復雜的元素,是我們通過簡單圖形管事構造文檔的方法之一。第一步,我們將字符和圖形從左到右排列形成文檔的一行,然后由多行形成一列,再由多列形成一頁。
我們將每一個重要元素表示成一個對象,就可以描述這種物理結構。它不僅包括字符、圖形等可見元素,也包括不可見、結構化的元素,如行和列。
通過用對象表示文檔的每一個字符和圖形管事,我們可以提高Lexi最佳設計的靈活性。
其中有兩個比較重要的地方:對象需要相應的類。為了一致性對待這些對象,我們這些類必須有兼容的接口。
圖元:
我們將為出現在文檔結構中的所有對象定義一個抽象類圖元(Glyph)。它的子類即定義了基本的圖形元素(像字符和圖形),又定義了結構元素(像行和列)。設計如下圖:
圖元的三個基本的職責:1.怎樣畫自己,2.他們占用多大的空間3.它們的父圖元和子圖元是什么
Glyph的接口:
virtual void Draw(window*) windows類為了在屏幕上表示文本和基本對象,定義了一下圖形操作。
virtual void Bounds(Rect &) 父圖元通常需要知道想子圖元需要占用多大的空間這樣的信息,以把它和其他圖元安排在一行上,保證不會相互覆蓋。Bounds操作返回圖元占用的矩形區域,它的返回的是包含該圖元的最小矩形的對角頂點。Glyph各個子類重新定義該操作,返回他們各自畫圖所用的矩形區域。
virtual bool Intersecs(const Point&) 判斷一個指定的點是否與圖元相交。任何時候用戶點擊文檔某處時,Lexi都能調用該操作確定鼠標所在的圖元和圖元結構。
virtual void Insert(Glyph*,int)Insert操作在整數Index指定的位置上插入一個圖元。
virtual void Remove(Glyph*)Remove操作移去一個指定的子圖元
virtual Glyph* Child(int)返回給定Index的子圖元,像這樣有子圖元的圖元應該內部使用Child草,而不是直接訪問子數據結構。
virtual Glyph*Parent() 返回它的父圖元
三.格式化
我們將“格式化”含義限制為將一個圖元集合分解為若干行。格式化(formatting)和分行(linebreaking)。這些技術同樣適用於將行分解為列和將列分解為頁。
封裝格式化算法:
格式化算法趨於復雜化,因而可以考慮將它們包含於文檔結構之中,但最好是將它們徹底獨立於文檔結構之外。理想情況下,我們能夠自由地增加一個Glypah子類而不用考慮格式化算法。反過來,增加一個格式化算法不應該要求修改已有的圖元類。
Compositor和Composition:
我們為能封裝格式化算法的對象定義一個Compositor類。它的接口可讓compositor獲知何時去格式化那些圖元。他所格式化的圖元是一個被稱為Compositon的特定的圖元的各個子圖元。一個Compositon在創建時得到一個Compositor子類實例,並在必要的時候讓Compositor對它的圖元作Composecoa。下面是他們的關系:
基本Compositor接口
void SetComposition(Composition*);
virtual void Compose();
其實,這個設計就是策略模式(Strategy),Strategy模式應用的關鍵點在於為Strategy和它的環境設計足夠的通用接口,以支持一系列的算法。你不必為了支持一個新的算法改變Strategy或他的環境。
四.修飾用戶界面
第一種是在文本編輯區域周圍邊界以界定文本頁。第二種事加滾動條讓用戶能看到同一頁的不同部分。
透明圍欄(Transparent Enclosure):1.單子女(單組件)組合2.兼容的接口
MonoGlyph
我們可以將透明圍欄的概念用於所有的修飾其他圖元的圖元。MonolGlyph保存了指向一個組件的引用並且傳遞所有的請求給這個組件。如下圖所示:
五 支持多種視覺感標准
視感標准本來是用來加強某一窗口平台上各個應用之間用戶界面的一致性的。這些標准定義了應用程序應該怎樣顯示和對用戶請求作出反映。最好能夠在運行時刻可以改變Lexi的改觀和感覺。
對象創建的抽象:1.第一個集合是由抽象Glyph子類構成的,對每一個窗口組件圖元都有一個抽象Glyph子類。
2.另一個集合是與抽象子類對應的實現不同視感標准的具體的子類的集合。
Abstract Factory模式:
工廠(Factory)和產品(Product)是Abstract Factory模式的主要參與者。該模式描述了怎樣在不直接實例化類的情況下創建一系列相關的產品對象。它最適用於產品對象的數目和種類不變,而具體產品系列之間存在不同的情況。
六.支持多種窗口系統
封裝實現依賴關系:顯示一個圖元或圖元結構的Windows類。
Windows類封裝了窗口要各個窗口系統都要做的一些事情:
- 它們提供了畫基本幾何圖形的操作
- 它們能辦廠圖標或還原幾何圖形的操作
- 它們能改變自己的大小
- 它們能夠更加需要畫出窗口內容。例如,當它們由圖標還原為窗口時,或它們在屏幕空間上重疊、出界的部分重新顯示時,都要重畫。
Windows類的窗口功能必須跨越不同的窗口系統,有兩種比較極端的觀點:
1.功能的交集 Windows類的接口只是提供所有窗口系統共有的功能
2.功能並集 創建一個合並了所有存在系統的功能的接口
我們一般采取Window類將提供一個支持大多數窗口系統的方便的接口。
Windows和WindowImp
我們定義一個獨立的WindowImp類層次來隱藏不同窗口系統的實現。為了使Lexi運行於一個特定的窗口系統,我們用該系統的一個WindowImp子類實例設置Window對象。下面的圖顯示了Window和WindowImp層次結構之間的關系。
通過在WindowImp類中隱藏實現,我們避免了對窗口系統的直接依賴,這可以讓Window類層次保持相對較小並且比較穩定。同時我們還能方便地擴展實現層次結構以支持新的窗口系統。
七,用戶操作
我們可以定義一個Command抽象類,以提供發送請求的接口,這個基本接口由一個抽象超值“Execute”組成。Command的子類以不同方式實現Execute操作,以滿足不同的請求。
撤銷和重做
在交互應用中撤銷和重做能力是很重要的。為了撤銷和重做一個命令,我們在Command接口中增加Unexecute操作。Unexecute操作是Execute的逆操作。為了決定一個命令是否可以撤銷,我們給Command接口增加了一個抽象的Reversible操作,它返回Boolean值。子類可以重定義這個操作,以根據運行情況返回true或false。
命令歷史記錄
支持任意層次的撤銷和重做命令的最后一步是定義一個命令歷史記錄(Command History)或已經執行的命令列表。
該模式描述了怎樣封裝請求,也描述了一致性的發送請求的接口,允許你配置客戶端以處理不同請求。
八、拼寫檢查和斷字處理
我們引入一個類稱之為iterators的對象,他們的目的是定義這些機制的不同集合。我們可以通過繼承來統一訪問不同的數據結構和支持新的遍歷方式,同時不改變圖元接口或打亂已有的圖元實現。
Iterator類及其子類
Iterator模式描述了那些支持訪問和遍歷對象結構的技術,它不僅可用於組合結構也可用於集合。該模式抽象了遍歷算法,對客戶隱藏了它所遍歷對象的內部機構。
現在貌似我們已經解決了圖元的遍歷的問題,下面我們就把圖元的分析加入其中。我們使用了Vistor模式。
我們使用術語訪問者(vistor)來泛指在遍歷過程中“訪問”被遍歷對象並做適當操作的一類對象。
我在邊看書,邊寫代碼,只是把書中的代碼稍微加工了一下,具體的功能實現,都沒有做,只是一個框架上的實現。
代碼下載:Lexi.rar