1. 程式人生 > >軟體設計的哲學: 第六章 更深的通用模組

軟體設計的哲學: 第六章 更深的通用模組

目錄

  • 6.1 使類具有一定的通用性
  • 6.2 示例:為編輯器儲存文字
  • 6.3更通用的API
  • 6.4 通用性使得資訊隱藏效果更好
  • 6.5 問自己的問題
  • 6.6 結論

在設計新模組時,最常見的一個決定就是以通用方式還是特殊方式實現它。有些人可能會說,應該採用通用的方法,即實現一種機制,用於解決廣泛的問題,而不僅僅是當前重要的問題。在這種情況下,新機制可能會在未來發現意想不到的用途,從而節省時間。通用方法似乎與第3章中討論的投資心態一致,即您預先花費更多的時間來節省以後的時間。

另一方面,我們知道很難預測軟體系統的未來需求,所以一個通用的解決方案可能包含一些實際上並不需要的設施。此外,如果您實現了一些過於通用的東西,那麼它可能無法很好地解決您現在面臨的特定問題。因此,有些人可能會說,最好關注今天的需求,只構建您知道自己需要的東西,並根據您今天計劃使用它的方式進行專門化。如果採用特殊用途的方法,並在以後發現其他用途,則始終可以對其進行重構,使其成為通用用途。專用方法似乎與軟體開發的增量方法一致。

6.1 使類具有一定的通用性

根據我的經驗,最好的方法是以某種通用的方式實現新模組。短語“某種程度上通用的”意思是模組的功能應該反映您當前的需求,但是它的介面不應該。相反,介面應該足夠通用,以支援多種用途。該介面應該易於使用,以滿足今天的需要,而不是專門針對他們。“有些”這個詞很重要:不要忘乎所以,不要構建一些通用的東西,因為它很難滿足您當前的需求。

通用方法最重要的(可能也是最令人驚訝的)好處是,它比專用方法產生更簡單、更深入的介面。如果您將該類用於其他目的,那麼通用方法還可以在將來為您節省時間。然而,即使該模組僅用於其原始目的,由於其簡單性,通用目的的方法仍然更好。

6.2 示例:為編輯器儲存文字

讓我們考慮一個來自軟體設計課程的例子,在這個課程中,學生被要求構建簡單的GUI文字編輯器。編輯器必須顯示一個檔案,並允許使用者指向、單擊和鍵入來編輯檔案。編輯器必須在不同的視窗中支援同一檔案的多個同步檢視;他們還必須支援多級撤銷和重做檔案的修改。

每個學生專案都包含一個管理檔案底層文字的類。文字類通常提供將檔案載入到記憶體、讀取和修改檔案文字以及將修改後的文字寫回檔案的方法。

許多學生團隊為text類實現了特殊用途的api。他們知道這個類將在互動式編輯器中使用,所以他們考慮了編輯器必須提供的特性,並根據這些特定的特性定製了文字類的API。例如,如果編輯器的使用者鍵入退格鍵,編輯器將立即刪除游標左邊的字元;如果使用者鍵入刪除鍵,編輯器將立即刪除游標右側的字元。瞭解了這一點,一些團隊在text類中建立了一個方法來支援這些特定的特性:

void backspace(Cursor cursor);
void delete(Cursor cursor);

這些方法中的每一個都以游標的位置作為引數;特殊型別遊標表示此位置。編輯器還必須支援可以複製或刪除的選擇。學生們通過定義一個選擇類,並在刪除期間將這個類的物件傳遞給text類來處理這個問題:

void deleteSelection(Selection selection);

學生們可能認為如果text類的方法對應於使用者可見的特性,那麼實現使用者介面會更容易。然而,在現實中,這種專門化對使用者介面程式碼幾乎沒有什麼好處,而且它為使用者介面或文字類的開發人員帶來了很高的認知負荷。text類以大量的淺層方法結束,每個淺層方法只適合一個使用者介面操作。許多方法(如delete)只在一個地方呼叫。因此,開發使用者介面的開發人員必須瞭解文字類的大量方法。

這種方法在使用者介面和文字類之間造成了資訊洩漏。與使用者介面相關的抽象,如選擇或退格鍵,反映在文字類中;這增加了開發人員處理文字類的認知負荷。每一個新的使用者介面操作都需要在text類中定義一個新方法,因此處理使用者介面的開發人員可能最終也要處理text類。類設計的目標之一是允許獨立地開發每個類,但是專門化的方法將使用者介面和文字類繫結在一起。

6.3更通用的API

更好的方法是使text類更通用。它的API應該只根據基本的文字特性來定義,而不反映將用它實現的高階操作。例如,只需要兩個方法來修改文字:

oid insert(Position position, String newText);
void delete(Position start, Position end);

第一個方法在文字中的任意位置插入任意字串,第二個方法刪除大於或等於開始但小於結束位置的所有字元。這個API還使用了一個更通用的型別Position而不是遊標,它反映了一個特定的使用者介面。text類還應該提供一些通用的工具來處理文字中的位置,例如:

Position changePosition(Position position, int numChars);

此方法返回一個新位置,該位置距離給定位置有一定數量的字元。如果numChars引數為正,則新位置在檔案中的時間晚於位置;如果數字是負數,則新位置在位置之前。該方法在必要時自動跳轉到下一行或上一行。使用這些方法,可以用以下程式碼實現delete鍵(假設遊標變數儲存當前遊標位置):

text.delete(cursor, text.changePosition(cursor, 1));

同樣,backspace鍵可以實現如下:

text.delete(text.changePosition(cursor, -1), cursor);

使用通用的文字API,實現使用者介面功能(如刪除和退格)的程式碼比使用專門的文字API的原始方法要長一些。然而,新程式碼比舊程式碼更明顯。在使用者介面模組中工作的開發人員可能關心backspace鍵刪除哪些字元。對於新程式碼,這是顯而易見的。使用舊的程式碼,開發人員必須轉到text類並閱讀backspace方法的文件和/或程式碼來驗證行為。此外,與專門化方法相比,通用方法總體上的程式碼更少,因為它用更少的通用方法替換了文字類中大量的專用方法。

使用通用介面實現的文字類可以用於互動編輯器之外的其他用途。例如,假設您正在構建一個應用程式,該應用程式通過用另一個字串替換所有特定字串的出現來修改指定的檔案。專門化文字類(如backspace和delete)中的方法對這個應用程式沒有什麼價值。但是,通用文字類已經具備了新應用程式所需的大部分功能。唯一缺少的是一個方法來搜尋下一個出現的給定字串,如這個:

Position findNext(Position start, String string);

當然,互動式文字編輯器可能具有搜尋和替換的機制,在這種情況下,text類已經包含此方法。

6.4 通用性使得資訊隱藏效果更好

通用方法在文字和使用者介面類之間提供了更清晰的分離,從而實現更好的資訊隱藏。文字類不需要知道使用者介面的細節,比如如何處理退格鍵;這些細節現在封裝在user interface類中。可以新增新的使用者介面特性,而無需在text類中建立新的支援函式。通用介面還減少了認知負擔:開發人員只需學習一些簡單的方法,這些方法可以用於各種目的。

text類的原始版本中的backspace方法是一個錯誤的抽象。它的目的是隱藏關於刪除哪些字元的資訊,但使用者介面模組確實需要知道這一點;使用者介面開發人員可能會閱讀backspace方法的程式碼以確認它的準確行為。將這個方法放到text類中只會讓使用者介面開發人員更難獲得他們需要的資訊。軟體設計最重要的元素之一是決定誰需要知道什麼,什麼時候需要知道。當細節很重要時,最好讓它們儘可能明確和明顯,比如backspace操作的修改實現。將這些資訊隱藏在介面後面只會造成不透明性。

6.5 問自己的問題

識別乾淨的通用類設計要比建立一個類容易。下面是一些您可以問自己的問題,這些問題將幫助您在介面的一般用途和特殊用途之間找到適當的平衡。

什麼是最簡單的介面可以滿足我當前的所有需求?如果您減少了API中的方法數量,而沒有減少它的整體功能,那麼您可能正在建立更通用的方法。 特殊用途的文字API至少有三種刪除文字的方法:backspace、delete和deleteSelection。更通用的API只有一個用於刪除文字的方法,這滿足了所有三個目的。只有在每個方法的API保持簡單的情況下,減少方法的數量才有意義;如果為了減少方法的數量,您不得不引入許多額外的引數,那麼您可能並沒有真正地簡化事情。

在多少情況下會使用這種方法?如果一個方法是為一個特定的用途而設計的,比如backspace方法,那麼這就是一個危險訊號,因為它可能太特殊了。看看是否可以將幾個專用方法替換為一個通用方法。

這個API容易用於我當前的需求嗎?這個問題可以幫助您確定什麼時候您在使API變得簡單和通用方面做得太過火了。如果您必須編寫大量額外的程式碼來使用一個類來滿足當前的需要,那麼介面沒有提供正確的功能就是一個危險的訊號。例如,text類的一種方法是圍繞單字元操作進行設計:insert插入單個字元,delete刪除單個字元。這個API既簡單又通用。但是,對於文字編輯器來說,它並不特別容易使用:高階程式碼將包含許多迴圈來插入或刪除字元範圍。對於大型操作,單字元方法的效率也很低。因此,文字類最好內建對字元範圍的操作的支援。

6.6 結論

與專用介面相比,通用介面有許多優點。它們往往更簡單,包含更少的方法。它們還提供了類之間更清晰的分離,而特殊用途的介面往往會洩漏類之間的資訊。使您的模組具有一定的通用功能是降低整個系統複雜性的最佳方法之