抽象這個東西,提及來很抽象,其實很簡單。java
抽象是什麼?按維基百科的說法:「在計算機科學中,抽象化(英語:Abstraction)是將數據與程序以它的語義來呈現出它的外觀,可是隱藏起它的實現細節。」這個定義也許還有些「抽象」,舉幾個例子來看,它其實簡單。 數據庫
「抽象」在咱們的平常工做和生活中比比皆是。例如,咱們常常會說,「我是一個開發」,「這事兒你得找產品」,這裏的「開發」、「產品」,都是一種抽象。它們定義了「開發要寫設計、寫代碼、寫單測」、「產品要寫ppt、寫word、寫excel」這樣一種「語義外觀」,可是它們本身並不會寫代碼或寫文檔,這些實現細節隱藏在「職位」之下、由具體的「員工」來完成。設計模式
在技術上,這樣的例子更是俯拾皆是。例如,Slf4j提供了一個日誌的抽象,它定義了「怎麼打印日誌」這個「語義外觀」,可是它隱藏了實際打印日誌的實現細節——是log4j、仍是logback?使用Slf4j時咱們是不知道的。還有,Jdbc的Driver、Connection和Statement定義了「怎麼操做數據庫」這個「語義外觀」,可是它也沒有實際去操做數據庫。這些實現細節是由抽象之下的具體實現來處理的。異步
即便是業務系統中,「抽象」的實例也隨處可見。一個設計良好的接口就是一個業務的抽象,它定義了這項業務支持哪些操做。例如,咱們有一個短信簽約接口定義了submit、sendCode、submitCode三個方法,本質上就是定義了「短信簽約操做有三個步驟」這樣一個業務抽象。至於每一個步驟都是如何實現的,這是底層邏輯的事情了——實際上,這三個步驟的底層用的是同一個方法。又如,咱們有一個凍結訂單的接口,定義了frozenFlow、frozenLimit、forzenTransport三個方法,這也就是定義了「凍結一筆訂單必須凍結Flow、Limit和Transport這三類數據」這樣一個業務抽象。至於這三類數據具體如何凍結麼——就咱們系統來講,有的業務是直接邏輯刪除,有的業務是把數據回滾到初始狀態,有的業務則乾脆不須要處理Transport數據——這又是底層邏輯須要考慮的事情了。 ide
因此,抽象是什麼?抽象就是這樣一個東西:它告訴了你本身能作什麼、但不告訴你它是怎麼作的。工具
設計出一個好的抽象,除了能隱藏底層實現以外,還有什麼好處嗎?咱們爲何要在「抽象」這虛的東西上下功夫呢? 性能
借用另外一篇文章的話來講:抽象設計得越好,代碼就越簡單易用;代碼可替代性就越好;可擴展性就越好。 測試
爲何說抽象設計得越好、代碼就越簡單易用呢?由於一個好的抽象設計隱藏了它的底層實現,使得咱們在使用它的時候,不須要關注底層的細節。就比如開自動擋的車時不用關心離合換擋的事兒,開起來固然比手動擋要簡單方便啦。優化
例如,咱們看看下面這個接口:設計
public interface QueryService{ public Bean queryFromRemote(long id); public Bean queryFromLocal(long id); }
這個接口提供了兩個方法,兩個方法的入參、出參都是如出一轍的,區別只在於方法名——以及名字所暗示的,是從「遠程」查詢、仍是從「本地」查詢。若是調用方在使用時,確實須要區分數據來源,這個設計倒也無可厚非。可是,實際上調用這兩個方法時,全部的代碼都是這個樣子的:
Bean bean = queryService.queryFromLocal(id); if(bean == null){ bean = queryService.queryFromRemote(id); } if(bean == null){ throw new Excepton(); }
這樣的代碼出現了至少五次。囉嗦嗎?囉嗦。麻煩嗎?麻煩。聞着臭嗎?臭。爲何每次調用這個接口時都要這麼寫呢?由於這個接口把本身底層的實現——是從遠程獲取數據、仍是從本地獲取數據——暴露出來了。換句話說,這個接口的抽象設計得不夠好。若是咱們把這個接口設計成這樣:
public interface QueryService{ public Beean query(long id); }
順便,底層這樣實現:
public class QueryServiceImpl{ public Bean query(log id ){ Bean bean = queryFromLocal(id); if(bean == null){ bean = queryFromRemote(id); } if(bean == null){ throw new RuntimeException(); } return bean; } }
那麼,咱們就能夠這樣調用這個接口了:
Bean bean = queryService.query(id);
這樣從新設計/實現過以後,使用起來是否是簡單、方便多了?這就是良好的抽象設計的第一個優勢。
爲何說抽象設計得好,代碼的可替代性就越好呢?這一樣是由於一個好的抽象設計隱藏了它的底層實現,不管咱們怎麼更換實現細節,只要對外抽象不變,調用方都不受影響。這就比如咱們去銀行櫃檯取錢:只要能把錢正確取出來,櫃員是男是女、是胖是瘦、甚至因而活人仍是機器,這都無所謂。
我參與設計過一套帳務系統,把全部帳戶間的轉帳操做所有抽象爲這樣一個接口,它所表達的業務含義是:從帳戶from向帳戶to轉入金額amount元,記帳科目是type:
public interface AccountService{ public void trans(Account from, Account to, Money amount, TransType type); }
在這個接口的「掩護」下,咱們更換過不少種底層實現方式:單邊帳、雙邊帳、會計科目記帳;同步操做、異步操做、批量操做;等等等等。沒有一次變動影響到了接口調用方,最終找到了既能知足全部業務功能、又提升了處理性能的最佳方案。這就是在好的抽象設計下的代碼可替代性帶來的好處。
也有反面例子。我參與設計過一套Java操做Excel文件的工具,底層用的是POI組件。這套工具的核心接口大概是這個樣子的:
public interface ExcelService<T>{ public List<T> read(HSSFWorkbook workBook); }
這個接口的功能,簡單來講就是傳入一個Excel文件、並把其中的數據解析爲對象T。它的主要問題在於:底層實現——也就是HSSFWorkbook——被暴露出來了。這就致使了這個接口只能解析2003版的Excel文件,面對用戶上傳的2007版Excel文件,它就無能爲力了。並且,若是要把工具升級到2007版,全部調用方都必須跟着一塊兒改:在咱們的系統裏,這意味着要多修改二十多處代碼、多回歸測試幾十個功能。其中的困難可想而知。
若是這個接口設計得更好,它的底層代碼的可替代性就更高,重構、優化、需求變動時須要修改的地方就更少。改得越少,開發的工做量、加班量就越少,出bug的概率也會更少。
爲何說抽象設計得好,代碼的可擴展性就越好呢?這和可替代性有類似之處:根子上仍是由於一個好的抽象設計能隱藏它的底層實現。就像家裏給小孩兒燉湯;媽媽去廚房嚐了一勺,而後多撒了一把蔥花;姥姥又去嚐了一勺,而後多加了點薑片;奶奶又去嚐了一勺,而後多加了點花椒……(最後留給小孩兒的就只剩一勺濃湯寶了哈哈)。
咱們有一個查銀行卡列表的接口,客戶端查到列表後,須要根據不一樣的場景來展現或「置灰」某些卡。例如,劃扣場景下,不支持自動劃扣的卡就必須置灰;解綁定場景下, 跟某些業務綁定的卡就必須置灰;業務綁卡場景下,已經跟該業務綁定的卡就必須置灰……等等等等。
咱們爲這個業務所設計的抽象是這樣子的:
public interface CardListService{ List<Card> query(long userId, Scene scene);} //核心實現是這樣的 public class CardListServiceImpl{ private Map<Scene, CardListService> serviceMap; public List<Card> query(long userId, Scene scene){ return serviceMap.get(scene).query(userId, scene); } } // 返回字段是這樣的 public class Card{ // 客戶端根據這個字段的值來判斷當前銀行卡是展現仍是置灰 private boolean enabled; // 其它卡號、銀行名等字段,和accessor略去 } // 入參是這樣的 public enum Scene{ DEDUCT, UN_BIND, BIND; }
客戶端不須要關注List<Card>
中的銀行卡是否是支持自動劃扣、是否是和某個業務綁定,只須要根據返回結果中的enabled字段來展現或置灰便可。由服務端來根據客戶端傳入的Scene來判斷這些卡是否應當展現。並且,不管哪一個Scene下要增長邏輯,或者要增長新的Scene,都只須要服務端作出修改,客戶端是不須要變的。並且即便是服務端,須要修改或增長的代碼量也不大,很是簡單。
簡單易用、可替代和可擴展這些,對於業務系統的重要性有時甚至比對技術中間件還要高。業務系統的一個重要特色,就是業務需求在不停變化、頻繁變化:今天需求是這樣,明天就推翻不作了,後天又從新提出來,大後天再改一版……若是系統的設計實現被需求牽着鼻子走,那開發就有改不完的代碼、加不完的班了。好好地設計一套業務抽象,讓系統和代碼簡單易用、易於替換、易於擴展,纔有可能在少修改代碼、甚至不修改代碼的基礎上去知足多變的業務需求。開發才能從業務代碼中釋放出來,去提高本身、優化系統。
怎樣設計一個好的抽象呢?其實咱們已經有不少方法論/工具箱了:高內聚/低耦合、封裝/繼承/多態、SOLID、設計模式……等等等等,不一而足。只不過之前討論它們的時候,更多地是在「就事論事」地討論它們自身,而並無考慮到它們與「抽象」的關係。怎樣從業務抽象的角度去理解和應用這些方法和工具、又怎樣運用它們來創建良好的業務抽象呢?下回分解吧。