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