如何擺脫工具類
無論是進行代碼review還是緊急編碼調整,你總會發現:你又搞出了一個幫助類(helper class)。代碼運行一切正常,進度又必須跟上,發布任務一個接一個,因此那個幫助類逐漸變成了一個提供了很多靜態(static)方法的“怪獸類” (monster class),在它的utils包內不受控制地增長。utils包長久以來就是一個技術爭議的荒蠻之地,面向對象設計理念連半步都不敢踏入。“工具類是功 能集中,并且邏輯毫不重復(Do not repeat yourself)” 一些開發人員會這樣喊道 ,通常就是他們編寫了這些工具類。因為所有都是靜態的,所以它很快 - 團隊里面的另外一些人這樣說,也許就是是添加另外一些靜態方法的人。它很容易使用,我們使這些代碼很簡潔 — 你可以在這個空間內聽到這樣的言論,但這又是另外一個對KISS的誤解了。
我們會爭論到:通常幫助類和工具類都很簡單,特別是當我們不能修改新功能的目標類(例如外部依賴庫)或我們不能找到使用的目標(不清晰的領域模 型,PoC,需求缺失),或者我們只是不想去找它(懶,這也是幫助類的最主要原因)。但是最大的問題在于這很明顯不是面向對象的解決方案,并且隨著時間的 推移(缺少團隊溝通,資源重用,快速修復和一些其他的東西)它會導致一些包含無盡靜態方法的容器和令人頭疼的維護(你想要做到DRY,但你卻是用10個方 法來提供幾乎相同的功能,盡管不是完全一樣;你想要快速,但你現在不能方便地添加一個cache機制到那個靜態類中或者你遇到了并發的麻煩;你想使事情變 得簡單,但現在你的IDE提供了一長列的各種各樣的方法,這并不能簡化你的工作)。但不要擔心,我們會嘗試著去解決它。
讓我們來重構幫助類
首先,我們需要定義我們的問題:一個只提供靜態方法的無狀態類(有Helper或Utils后綴),它沒有明確的職責,在項目中也不會被初始化為對象。
接著,我們需要一個幾乎明確的方案來解決問題。這幾乎就代表了例外和項目特性:最后的決定當然是根據具體的情況來了,任何被稱為通用解決方案的基本上都可以忽略。我們最后需要分析一下給出的類,嘗試著:
- 找到一個確定靜態方法從屬的目標類
- 或找到這個類實際提供的目標業務實體,然后把它遷移到相關的組件,重命名并且刪除靜態方法(替換它們)
- 或者通過面向對象方式添加一個提供一個或多個行為(之前存在的靜態方法)新類。 </ul>
- 為了使我們的任務簡單些,我們刪掉項目的幫助類中沒用的方法(你的IDE將會幫你大忙)。
- 接下來我們把class定義為final。你看到項目中有編譯錯誤了嗎?如果有,為什么幫助類或工具類需要被繼承呢?你也許已經有一個目標:子類。如果子類是另外一個幫助類(真的嗎?),把它和父類合并吧。
- 如果不存在,我們為該類添加一個私有構造函數。你看到項目中出現了編譯錯誤了嗎?那么肯定在哪個地方初始化了這個類,所以這并不是單純的幫助類或者它沒有被正確使用。看一下那些調用方,你會發現一個或一系列方法都可能屬于這個目標類(或者實體)。
- 讓我們通過一定規則類似的簽名來分組類方法,將它們拆分到更小的幫助類中(從繁雜到有共性的方法,那個共性也許就是我們需要的目標實體了)。通常
到了這一步,我們會從一個大的工具類向更輕量的幫助類過渡(提示:這時候不要害怕創建一個只有一個方法的類),同時我們的范圍縮小了(從
ProjectUtils
到CarHelper
,EngineHelper
,WheelHelper
等等)。(好,你的代碼難道看起來不是更簡潔了嗎?) -
如果這些新類只有一個方法,我們需要看一下它的用途。如果我們只有一個調用者,那么恭喜你,那就是我們的目標類了!你可以把方法移到類中,作為behavior或私有方法(保持它的static標識或者利用內部狀態)。這個幫助類就消失了。
</li> -
我們目前得到的幫助類(但是它確實可以成為你的起點)確定了這些關聯方法的一個通用狀態。提示:看一下那些方法中的大部分通用參數(例如,所有方法都接收一個
</li>Car
對象),這表明,這些方法可能應該作為方法屬于Car
類(或者擴展?封裝類?)。否則,這些通用的參數應該是一個可以傳給構造函數并且被所有(非靜態和其他的)方法使用的類的屬性,狀態。那個屬性應該會使你想起類的前綴,方法的歸類可以使你想起一系列行為的類(CarValidator
,CarReader
,CarConverter
等等)。那么這個幫助類又可以去掉了。 - 如果這堆方法根據可選的輸入和一些相同輸入參數來使用不同的參數,那么考慮通過使用建造者模式(Builder pattern)定義可變的接口來轉換這個幫助類:從一系列類似
Helper.calculate(x)
,calculate(x, y)
,calculate(x, z)
,calculate(y, z)
的靜態方法我們可以簡單地想到如newBuilder().with(x).with(y).calculate()
。幫助類會提供behaviours,減少業務方法列表,并且提供更好的擴展性。調用方可以把它當作內部屬性來重用或者在需要的時候再初始化。這個幫助類(我們所知的)又可以去掉了。 -
如果幫助類提供的方法確實是供不同的參數使用的(但,在這個時候,都是用于同一對象的),可以考慮使用命令模式(Command pattern):調用方實際上創建必須的命令(處理必須的輸入和提供必要的操作),在確定的上下文情況下會有一個調用者進行執行。你也許可以獲取到每個 靜態方法的命令實現,你的代碼也從
</li>Helper.calculate(x,y)
,calculate(z)
變成了invoker.calculate(new Action(x, y))
。幫助類再見。 -
如果幫助類提供的方法接收相同的參數,但處理不同的邏輯,可以考慮使用策略模式(
</li>Strategy pattern
):每一個靜態方法都可以簡單地變成一個策略實現,從而消除原來的幫助類(取而代之的是上下文組件)。 - 如果需要處理的多個靜態方法涉及到一個類層次或一系列的組件,可以考慮使用訪問者模式(
Visitor pattern
):你可以根據不同的訪問方法得到幾個訪問者實現,這也許可以替換部分或所有之前存在的靜態方法。 - 如果之前的情況都不符合你的情況,那可以使用三個最重要的指標:你的經驗,你的項目能力和直覺。 </ol>
上面的任何方案都可以提供一個更好的模型。然后我們再依據下面的步驟(假設根據下面的步驟進行項目重構):
總結
過程很簡單,找到對的實體和合理的目標類或者通過一種采用面向對象設計的標準方法來重構給定的幫助類(但會在代碼復雜度上有所增加,值得嗎?)。過 一下上面提到的場景列表,也許當你嘗試理解怎么去實現重構時會有多于一個將會為你提供靈感;特定的限制也許會限制已確定的解決方案;復雜的靜態方法和相關 的流程也許需要幾個重構的步驟,可以一直優化它直到得到可接受的結果。或者你可以選擇在某種程度上以代碼可讀性和簡單性的名義來維持原來的幫助類(希望能滿足上面至少5個步驟)。幫助類并不都是有害的,但絕大多數情況下你并不需要它們。
原文鏈接: javacodegeeks 翻譯: ImportNew.com - 陳 曉舜參考: 如休整脫離幫助類和工具類參考自我們的JCG成員 Antonio Di Matteo重構的建議 。
譯文鏈接: http://www.importnew.com/11593.html