本文來自網易雲社區設計模式
做者:陸秋煒
架構
引言 :好久以前,在作中間件測試的時候,看到開發人員寫的代碼,有人的代碼,看起來老是特別舒服,但有的開發代碼,雖然邏輯上沒有什麼問題,但總給人感受特別難受。後來成爲了一位專職開發人員,漸漸發現,本身的代碼也是屬於「比較難受」的那種。後來隨着代碼的增長,編寫代碼時,總有一些比較乖巧的方式,這就是以前不懂的「設計模式」。以前代碼架構比較少(只是寫一些測試工具),用不到這些,只有本身慢慢作了一些架構工做後,才用獲得,並去主動了解。ide
但今天想說的,並非具體的哪種設計模式的優劣,而是想記錄一下,設計模式中存在的一些設計思想。有了這些設計思想,某些設計模式就天然而然的出現了。因此說,所謂的「設計模式」並非被髮明出來的,而是被咱們本身「發現」的。工具
以前不管是做爲開發仍是測試,習慣性的以爲,別人提供了什麼功能,就用什麼樣的功能,這樣作天經地義。然而,在本身的架構設計過程當中,若是有了這樣額思惟,很容易讓本身的程序設計陷入困境。學習
打個裝修的比喻,咱們必定是有設計師設計相關方案(具體的風格),而後分解成對應的傢俱,而後再購買材料,打造對應的傢俱。若是咱們將這一過程倒過來,先有什麼材料,而後看這些材料能打造出什麼傢俱,再把傢俱組合起來,那麼最後的裝修效果必定會很是差。測試
圖1 正確的設計方式編碼
圖2 自底向上的設計結果,必定是最後的整合有問題spa
因此優秀的設計必定是從總體到局部設計出來的。從局部構造總體,不可能獲得優秀的設計。架構設計
瞭解清楚某個功能模塊(或者整個功能)具體要幹什麼事情,咱們纔可以知道具體要如何作設計。而不是找一個設計方案,可以實現主要功能就好了,其餘功能再次基礎上修修補補。設計
再舉一個簡單的例子:好比說咱們要喝水(表面功能/基礎目標),那麼咱們就須要找相關盛水的容器(設計實現)。咱們找到了如下容器(可能的實現方案):
圖三 各類盛水容器的實現
三種容器都能喝水,但具體要使用哪一個呢?若是隨便選一個酒杯,但具體實現(或者將來可能的功能)要求可以帶到戶外去,總不能給酒杯再加個蓋子吧;同理,若是咱們要品酒,卻選了個保溫杯的實現,到時候直接設計推倒重來了。因此,要有合適的設計,必定要對產品自己的需求(以及將來可能的需求)作詳細的分析和了解,而後肯定設計方案。
在學習「面向對象」的語言時,咱們首先被教會「封裝、繼承、多態」。今後,感受有點關係的都要進行繼承,以爲這樣能節省好多代碼。而後咱們的代碼中便出現了繼承的亂用
正常狀況下,這樣作沒有問題,但問題的起源在於,咱們的需求是不斷的修改和添加的,若是使用了繼承,在超類中的方法改動,會影響到子類,並可能引發引發子類之間出現冗餘代碼。
舉個汽車的例子吧,一輛汽車開行(drive)是同樣的,但車標(logo)是不同的,因此用繼承
public abstract class Car { /** * 駕駛汽車 */ public void drive(){ System.out.print("drive"); } /** * 每輛車的車標是不同的,因此抽象 */ public abstract void logo() ; }class BMW extends Car{ @Override public void logo() { System.out.print("寶馬"); } }class Benz extends Car{ @Override public void logo() { System.out.print("奔馳"); } }class Tesla extends Car{ @Override public void logo() { System.out.print("特斯拉"); } }
一切看起來解決的很完美。忽然加了一個需求,要求有個充電(change)需求,這時候,只有特斯拉(tesla)纔有充電方法。但若是使用繼承,在父類添加change方法的同時,就須要在BMW和Benz實現無用的change方法,對於子類的影響很是大。但若是使用組合,使用ChangeBehavior,問題就獲得了有效解決,
public interface ChargeBehavior { void charge() ; }public abstract class Car { protected ChargeBehavior chargeBehavior ; /** * 駕駛汽車 */ public void drive(){ System.out.print("drive"); } /** * 每輛車的車標是不同的,因此抽象 */ public abstract void logo() ; /** * 充電 */ public void change(){ /** * 不用關心具體充電方式,委託ChargeBehavior子類實現 */ if (chargeBehavior!=null) { chargeBehavior.charge(); } } }class Benz extends Car{ @Override public void logo() { System.out.print("奔馳"); } }class BMW extends Car{ @Override public void logo() { System.out.print("寶馬"); } }class Tesla extends Car{ @Override public void logo() { System.out.print("特斯拉"); } public Tesla() { super(); chargeBehavior = new TeslaChargeBehavior() ; } }class TeslaChargeBehavior implements ChargeBehavior{ @Override public void charge() { System.out.print("charge"); } }
經過將充電的行爲委託給changeBehavior接口,子類若是不須要的話,就能夠作到無感知接入。
這樣的代碼有三個優點
1,代碼不須要子類中重複實現
2,子類不想要的東西,能夠無感知實現
3,子類運行的行爲,能夠委託給behavior實現,子類本省自己無需任何改動
在剛剛接觸面向對象的時候,封裝,對咱們來講就是類,實例化後就是對象。最基本功能是對於數據進行隱藏,對於行爲進行開放(如JavaBean)。慢慢用多了之後漸漸發現,其實咱們能夠封裝跟多東西,好比某些實現的細節(私有方法方法),實例化規則(構造器)等。
因爲咱們的代碼是分層和分模塊的,但咱們的需求又是常常要變化的,咱們但願修改新功能,對於除了模塊自己外,調用方是無感知的。因此,咱們的類(或者說是模塊吧)變封裝了變化自己。對於調用方來講,只須要知道不會變的功能名(方法名)就夠了,而不須要了解可能變化的內容。
圖四 變化自己進行封裝
在一類實現中,咱們其實能夠分析發現,代碼的實現上是有一些共性的,好比說處理的流程(如何調用一些方法的順序),也有一些徹底一致的操做(好比上文提到的car均可以drive,實現一致的方法)。但也有一些可變性:如必須存在(共性),但實現不一致的操做(如上文car裏面的logo方法,必須有,但不一致)。這時候,咱們就能夠對這些實現進行一些簡單的抽象,成爲抽象類。抽象類就是將共性變爲以實現的方法,而將可變性變爲抽象方法,讓子類予以實現。
圖五,共性和抽象類
代碼看多了,寫多了,便會發現,看起來舒服的代碼,在可維護性,可讀性,可擴展性上相對來講都比較高。代碼界也有「顏值即戰鬥力」這一說法,很有一番玄學的味道。但分析具體的緣由,其實能夠發現,優秀的編碼設計,在其抽象,封裝,都有其合理之處,其總體的架構設計上,亦有其獨到之處。
網易雲大禮包:https://www.163yun.com/gift
本文來自網易雲社區,經做者陸秋煒受權發佈
相關文章:
【推薦】 流式斷言器AssertJ介紹