「意圖導向編程」,Programming by Intention
,也稱目的導向編程/自頂向下編程.
其基本思想是:
每個問題均可以分解成一系列的功能性步驟,在寫代碼的過程當中,會按照順序有意識的去完成每個步驟;而意圖導向編程則是先假設每個步驟都有一個理想的方法來完成,而不關注每一個步驟的具體實現,在這種狀況下,須要關心的是每一個方法的輸入參數,返回值以及什麼樣的名字最符合它的含義。算法
例如,建立一個服務,它接受一個業務交易,而後提交,具體的需求以下:編程
- 交易信息開始於一串標準的ASCII字符串。
- 這個信息字符串必須轉換成一個字符串的數組,數組存放的值是這次交易用到的領域語言(domain language)中所包含的詞彙元素(token)。
- 每個詞彙元素必須標準化(第一個字母大寫,其他字母小寫,空格和非字符數字的符號都要刪掉)。
- 超過150個詞彙元素的交易,應該採用不一樣於小型交易的方式(不一樣的算法)來提交,以提升效率。
- 若是提交成功,API方法返回true,不然返回false。
基於「意圖導向編程」的思想,咱們假設有一個類,類中有一個API實現上面的服務:設計模式
public class Transaction{ public Boolean commit(String command){ Boolean result = true; String[] tokens = tokenize(command); normalizeTokens(tokens); if(isALargeTransaction(tokens)){ result = processLargeTransaction(tokens); }else{ result = processSmallTransaction(tokens); } return result; } }
commit()
方法是程序API
,用於提供服務,而其餘方法(tokenize()、normalizeTokens()、isALargeTransaction()、processLargeTransaction()、processSmallTransaction()
)都不屬於這個對象API
,僅僅是實現過程當中的功能性步驟,稱之爲「輔助方法」(helper methods
),暫時能夠將他們視爲私有方法。數組
經過這樣的編碼方式,能夠將精力集中在如何分解最終目標,以及那些全局性的問題上。
而且這種實現方式,與直接把全部代碼寫到一個很長的方法裏相比並無增長工做量,不一樣點在於思考的方式和編碼的順序(先理清總體流程,再深刻細節)。dom
若是遵循意圖導向編程,那麼代碼將會:函數
代碼的質量標準之一就是內聚性。
以類爲例,每一個類都應該根據職責來定義,而且應該只有一個職責。類內部包括方法、狀態以及與其餘對象之間的關係,若是各個方面都緊密相關,而且圍繞着這個類的惟一職責,則說這個類的內聚性很強。單元測試
若是一個方法只實現總體職責中一個單獨的功能點,則說這個方法的內聚性很強。測試
人的思惟方式是單線程的,當進行「多任務」的時候,其實是在多個任務之間快速切換而已,人們仍舊習慣於一次只思考一件事情。意圖導向編程正是利用這一事實,用思惟鏈條單一性的特定去建立一樣具有單一性的內聚方法。優化
經過閱讀最初的實例代碼:編碼
public class Transaction{ public Boolean commit(String command){ Boolean result = true; String[] tokens = tokenize(command); normalizeTokens(tokens); if(isALargeTransaction(tokens)){ result = processLargeTransaction(tokens); }else{ result = processSmallTransaction(tokens); } return result; } }
能夠發現該服務的實現流程是:
獲取到一個指令,而後對指令進行分詞,再把分詞後獲得的指令標準化,判斷須要進行交易處理的類型,根據判斷結果來決定進行大型事務仍是小型事務的處理,最後返回結果。
由於上面的代碼只涉及到「作什麼」,而不是具體的「如何作」,這種狀況下,不須要註釋也能讀懂代碼的基本邏輯,這得益於規範的命名和步驟的清晰界定。
考慮下面的實現方式:
public class Transaction{ public Boolean commit(String command){ Boolean result = true; // tokenize the string some code here some more code here even some more code here that sets tokens // normalize the tokens some code here that normalize tokens some more code here that normalize tokens even some more code here that normalize tokens // see if you have a large transaction code that determines if you have a large transaction set lt = true if you do if(lt){ // process large transaction some code here to process large transaction some more code here to process large transaction }else{ // process small transaction some code here to process small transaction some more code here to process small transaction } return result; } }
上面的實現方式是將全部邏輯寫在一個大方法中,而且加了詳盡的註釋,但與意圖導向編程的實現方式相比,註釋顯得很沒有必要,而且代碼太多,給人的心理無形中形成一種壓力。
在程序的代碼錯誤修復過程當中,尋找錯誤所在纔是最花時間的。在遵循意圖導向編程時,因爲一個方法只作一件事,這個時候,若是出現錯誤,則可試試下面的辦法:
相比於費力的查閱一大段複雜的代碼,這種調試方法發現代碼錯誤的速度要快不少。
重構系統:保持系統行爲不變的狀況下,更改它的結構。
加強系統:增長或修改系統的行爲以符合新的需求。
重構一般認爲是「清理」系統中寫得糟糕的代碼,重構的一個基本實現方式是:把一部分代碼從一個巨大的方法中抽取出來,放到一個屬於它本身的新方法中,而在原來代碼中的那個位置直接調用這個新方法。
因爲原來方法的一部分臨時變量也須要遷移到新方法中,因此須要多個步驟才能完成一個函數的提煉。
若是採用意圖導向編程,一開始就是輔助方法了,只須要把公用的輔助方法遷移到其餘類便可。這樣的重構是很快的(複製-粘貼)。
當系統實現後,有新需求加進來了,如:與第三方程序交互時,因爲第三方程序的緣由,再也不支持某些舊版詞彙,這個時候須要更新一個詞彙元素,如:
public class Transaction{ public Boolean commit(String command){ Boolean result = true; String[] tokens = tokenize(command); normalizeTokens(tokens); updateTokens(tokens); if(isALargeTransaction(tokens)){ result = processLargeTransaction(tokens); }else{ result = processSmallTransaction(tokens); } return result; } }
有新需求加進來的時候,只須要在API
方法的實現流程中增長updateTokens()
方法,其餘都不須要修改到,把影響降到了最低。
若是修改了標準化的算法,則更改normalizeTokens()
方法便可,其餘都無需改動。在修改的過程當中,代碼能很快定位。
設計的基本建議:使用服務的客戶端,在設計時應該遵守的是服務的接口定義,而不是服務的具體實現。
在上面的實現中,輔助方法被定義成了私有方法,是爲了避免想與外部對象發生關聯,但這種狀況下就不利於方法的單元測試。
咱們只能對commit()
方法進行單元測試,即測試服務的總體行爲。此時測試狀況比較複雜,會有不少種因素致使測試失敗。
能夠有以下解決辦法:
- 若是輔助方法只是實現單個服務的一部分,則不必單獨測試輔助方法,測試這個服務流程便可。
- 若是某些輔助方法是能被其餘服務使用到的,則須要將該輔助方法單獨到其餘的類中,而且定義成公有的方法,則對原來輔助方法的調用就變成了對新類方法的調用,而且新類的公有方法是能進行單元測試的。
爲了提升類的內聚性,須要把這個類不該該有的方法遷移到其餘類中,這樣可讓這個類所關注的東西減小。
意圖導向編程建立的方法只完成一個功能,這樣避免了遷移方法是常常遇到的問題:包含不能遷走的部分。
當一個方法只作一件事時,要麼所有遷移,要麼不遷移。
方法遷移難,還可能因爲它直接關聯到了類中的狀態變量,在使用意圖導向編程時,習慣於將參數傳遞到輔助方法,而後獲取一個返回結果,而不是讓方法直接使用對象的狀態。
從以前的重構和加強可當作,當增長需求時,只須要在流程中增長對應的輔助方法;
當須要修改需求時,只須要修改對應的輔助方法。這種修改和擴展容易定位而且不影響其餘代碼。
上面的例子中,若是有兩個不一樣的交易類型,流程步驟同樣(分詞、標準化、更新、處理),但每一步的實現方式不同。
使用意圖導向編程時,處理每一個輔助方法具體實現不同,commit()
方法是同樣的,這個時候,能夠很容易的應用模板方法模式
。