如何擺脫工具類

edithfang發表於2014-06-13

無論是進行程式碼 review 還是緊急編碼調整,你總會發現:你又搞出了一個幫助類(helper class)。程式碼執行一切正常,進度又必須跟上,釋出任務一個接一個,因此那個幫助類逐漸變成了一個提供了很多靜態(static)方法的“怪獸類”(monster class),在它的 utils 包內不受控制地增長。utils 包長久以來就是一個技術爭議的荒蠻之地,物件導向設計理念連半步都不敢踏入。“工具類是功能集中,並且邏輯毫不重複(Do not repeat yourself)” 一些開發人員會這樣喊道 ,通常就是他們編寫了這些工具類。因為所有都是靜態的,所以它很快 - 團隊裡面的另外一些人這樣說,也許就是是新增另外一些靜態方法的人。它很容易使用,我們使這些程式碼很簡潔 — 你可以在這個空間內聽到這樣的言論,但這又是另外一個對 KISS 的誤解了。

我們會爭論到:通常幫助類和工具類都很簡單,特別是當我們不能修改新功能的目標類(例如外部依賴庫)或我們不能找到使用的目標(不清晰的領域模型,PoC,需求缺失),或者我們只是不想去找它(懶,這也是幫助類的最主要原因)。但是最大的問題在於這很明顯不是物件導向的解決方案,並且隨著時間的推移(缺少團隊溝通,資源重用,快速修復和一些其他的東西)它會導致一些包含無盡靜態方法的容器和令人頭疼的維護(你想要做到 DRY,但你卻是用 10 個方法來提供幾乎相同的功能,儘管不是完全一樣;你想要快速,但你現在不能方便地新增一個 cache 機制到那個靜態類中或者你遇到了併發的麻煩;你想使事情變得簡單,但現在你的 IDE 提供了一長列的各種各樣的方法,這並不能簡化你的工作)。但不要擔心,我們會嘗試著去解決它。

讓我們來重構幫助類

首先,我們需要定義我們的問題:一個只提供靜態方法的無狀態類(有 Helper 或 Utils 字尾),它沒有明確的職責,在專案中也不會被初始化為物件。

接著,我們需要一個幾乎明確的方案來解決問題。這幾乎就代表了例外和專案特性:最後的決定當然是根據具體的情況來了,任何被稱為通用解決方案的基本上都可以忽略。我們最後需要分析一下給出的類,嘗試著:

  • 找到一個確定靜態方法從屬的目標類
  • 或找到這個類實際提供的目標業務實體,然後把它遷移到相關的元件,重新命名並且刪除靜態方法(替換它們)
  • 或者通過物件導向方式新增一個提供一個或多個行為(之前存在的靜態方法)新類。

上面的任何方案都可以提供一個更好的模型。然後我們再依據下面的步驟(假設根據下面的步驟進行專案重構):

  1. 為了使我們的任務簡單些,我們刪掉專案的幫助類中沒用的方法(你的 IDE 將會幫你大忙)。
  2. 接下來我們把 class 定義為 final。你看到專案中有編譯錯誤了嗎?如果有,為什麼幫助類或工具類需要被繼承呢?你也許已經有一個目標:子類。如果子類是另外一個幫助類(真的嗎?),把它和父類合併吧。
  3. 如果不存在,我們為該類新增一個私有建構函式。你看到專案中出現了編譯錯誤了嗎?那麼肯定在哪個地方初始化了這個類,所以這並不是單純的幫助類或者它沒有被正確使用。看一下那些呼叫方,你會發現一個或一系列方法都可能屬於這個目標類(或者實體)。
  4. 讓我們通過一定規則類似的簽名來分組類方法,將它們拆分到更小的幫助類中(從繁雜到有共性的方法,那個共性也許就是我們需要的目標實體了)。通常到了這一步,我們會從一個大的工具類向更輕量的幫助類過渡(提示:這時候不要害怕建立一個只有一個方法的類),同時我們的範圍縮小了(從ProjectUtils到CarHelper,EngineHelper,WheelHelper等等)。(好,你的程式碼難道看起來不是更簡潔了嗎?)
  5. 如果這些新類只有一個方法,我們需要看一下它的用途。如果我們只有一個呼叫者,那麼恭喜你,那就是我們的目標類了!你可以把方法移到類中,作為 behavior 或私有方法(保持它的 static 標識或者利用內部狀態)。這個幫助類就消失了。
  6. 我們目前得到的幫助類(但是它確實可以成為你的起點)確定了這些關聯方法的一個通用狀態。提示:看一下那些方法中的大部分通用引數(例如,所有方法都接收一個Car物件),這表明,這些方法可能應該作為方法屬於Car類(或者擴充套件?封裝類?)。否則,這些通用的引數應該是一個可以傳給建構函式並且被所有(非靜態和其他的)方法使用的類的屬性,狀態。那個屬性應該會使你想起類的字首,方法的歸類可以使你想起一系列行為的類(CarValidator,CarReader,CarConverter等等)。那麼這個幫助類又可以去掉了。
  7. 如果這堆方法根據可選的輸入和一些相同輸入引數來使用不同的引數,那麼考慮通過使用建造者模式(Builder pattern)定義可變的介面來轉換這個幫助類:從一系列類似Helper.calculate (x),calculate (x, y),calculate (x, z),calculate (y, z)的靜態方法我們可以簡單地想到如newBuilder () .with (x) .with (y) .calculate ()。幫助類會提供 behaviours,減少業務方法列表,並且提供更好的擴充套件性。呼叫方可以把它當作內部屬性來重用或者在需要的時候再初始化。這個幫助類(我們所知的)又可以去掉了。
  8. 如果幫助類提供的方法確實是供不同的引數使用的(但,在這個時候,都是用於同一物件的),可以考慮使用命令模式(Command pattern):呼叫方實際上建立必須的命令(處理必須的輸入和提供必要的操作),在確定的上下文情況下會有一個呼叫者進行執行。你也許可以獲取到每個靜態方法的命令實現,你的程式碼也從Helper.calculate (x,y),calculate (z)變成了invoker.calculate (new Action (x, y))。幫助類再見。
  9. 如果幫助類提供的方法接收相同的引數,但處理不同的邏輯,可以考慮使用策略模式(Strategy pattern):每一個靜態方法都可以簡單地變成一個策略實現,從而消除原來的幫助類(取而代之的是上下文元件)。
  10. 如果需要處理的多個靜態方法涉及到一個類層次或一系列的元件,可以考慮使用訪問者模式(Visitor pattern):你可以根據不同的訪問方法得到幾個訪問者實現,這也許可以替換部分或所有之前存在的靜態方法。
  11. 如果之前的情況都不符合你的情況,那可以使用三個最重要的指標:你的經驗,你的專案能力和直覺。

總結

過程很簡單,找到對的實體和合理的目標類或者通過一種採用物件導向設計的標準方法來重構給定的幫助類(但會在程式碼複雜度上有所增加,值得嗎?)。過一下上面提到的場景列表,也許當你嘗試理解怎麼去實現重構時會有多於一個將會為你提供靈感;特定的限制也許會限制已確定的解決方案;複雜的靜態方法和相關的流程也許需要幾個重構的步驟,可以一直優化它直到得到可接受的結果。或者你可以選擇在某種程度上以程式碼可讀性和簡單性的名義來維持原來的幫助類(希望能滿足上面至少 5 個步驟)。幫助類並不都是有害的,但絕大多數情況下你並不需要它們。

譯文連結: http://www.importnew.com/11593.html

英文原文:How to get rid of helper and utils classes

本文轉載自: www.importnew.com

相關閱讀
評論(0)

相關文章