? ? ? ?最近為項目組整理相關項目相關的規(guī)范文檔盛龄,并整理了項目中,代碼中的一些問題芳誓,識別項目代碼中的一些可以優(yōu)化的代碼余舶,為項目后續(xù)的重構提供一些理論基礎,下面是為項目組編寫的一些技術開發(fā)需要了解的概念锹淌,雖然內容看上去不多匿值,但是以下的內容想要真正理解其中的含義,并在項目開發(fā)過程中合理實現赂摆,且不是一件容易的事挟憔,可能幾年都不一定能夠真正理解,真正體會其中的思想库正,下面就一起看看吧曲楚。
相關知識點匯總:
一:命名規(guī)范與注解?
二:阿里巴巴開發(fā)手冊簡介?
三:高內聚,低耦合褥符,易復用的思想?
四:面向對象的三大基本特征(封裝龙誊、繼承和多態(tài))?
五:23種設計模式初探(創(chuàng)建型,結構型喷楣,行為型)?
六:面向對象的七大設計原則?
七:重構的注意事項與技術實現(重構-改善既有代碼的設計)?
八:阿里巴巴代碼規(guī)范插件的使用?
九:擴展閱讀
一:命名規(guī)范與注解
命名規(guī)范參考網址:http://www.reibang.com/p/b7a644ea0d25
注解實現:
例子:/**?
? ? ?* @author 開發(fā)者姓名?
? ? ?* @description:??創(chuàng)建類的功能?
? ? ?* @date :${DATE} ${TIME}?
? ? */
編寫函數的注釋:
二:阿里巴巴開發(fā)手冊簡介
詳情請參考網址:
附加:命名規(guī)范如有沖突趟大,請以:http://www.reibang.com/p/b7a644ea0d25的為準。
三:高內聚铣焊,低耦合逊朽,易復用的思想
內聚性:
? 內聚:故名思議,表示內部間聚集曲伊、關聯的程度叽讳,那么高內聚就是指要高度的聚集和關聯追他。高內聚是指類與類之間的關系而定,高岛蚤,意思是他們之間的關系要簡單邑狸,明了,不要有很強的關系涤妒,不然单雾,運行起來就會出問題。一個類的運行影響到其他的類她紫。由于高內聚具備可靠性硅堆,可重用性,可讀性等優(yōu)點贿讹,模塊設計推薦采用高內聚渐逃。內聚標志一個模塊內各個元素彼此結合的緊密程度,它是信息隱蔽和局部化概念的自然擴展围详。內聚是從功能角度來度量模塊內的聯系朴乖,一個好的內聚模塊應當恰好做一件事。它描述的是模塊內的功能聯系助赞。
? 偶然內聚:一個模塊內的各處理元素之間沒有任何聯系,只是偶然地被湊到一起袁勺。這種模塊也稱為巧合內聚雹食,內聚程度最低。
? 邏輯內聚:這種模塊把幾種相關的功能組合在一起期丰, 每次被調用時群叶,由傳送給模塊參數來確定該模塊應完成哪一種功能 。
? 時間內聚:把需要同時執(zhí)行的動作組合在一起形成的模塊稱為時間內聚模塊钝荡。
? 過程內聚:構建或者操作的組合方式時街立,允許在調用前面的構建或操作之后,馬上調用后面的構件或操作埠通,即使兩者之間沒有數據進行傳遞赎离。簡單的說就是如果一個模塊內的處理元素是相關的,而且必須以特定次序執(zhí)行則稱為過程內聚端辱。例如某要完成登錄的功能梁剔,前一個功能判斷網絡狀態(tài),后一個執(zhí)行登錄操作舞蔽,顯然是按照特定次序執(zhí)行的荣病。
? 通信內聚:指模塊內所有處理元素都在同一個數據結構上操作或所有處理功能都通過公用數據而發(fā)生關聯(有時稱之為信息內聚)。即指模塊內各個組成部分都使用相同的數據結構或產生相同的數據結構渗柿。
? 順序內聚:一個模塊中各個處理元素和同一個功能密切相關个盆,而且這些處理必須順序執(zhí)行,通常前一個處理元素的輸出時后一個處理元素的輸入。例如某要完成獲取訂單信息的功能颊亮,前一個功能獲取用戶信息鸡岗,后一個執(zhí)行計算均價操作,顯然該模塊內兩部分緊密關聯编兄。順序內聚的內聚度比較高轩性,但缺點是不如功能內聚易于維護。
? 功能內聚:模塊內所有元素的各個組成部分全部都為完成同一個功能而存在狠鸳,共同完成一個單一的功能揣苏,模塊已不可再分。即模塊僅包括為完成某個功能所必須的所有成分件舵,這些成分緊密聯系卸察、缺一不可。
增強內聚度方法:
1铅祸、模塊只對外暴露最小限度的接口坑质,形成最低的依賴關系。
2临梗、只要對外接口不變涡扼,模塊內部的修改,就不得影響其他模塊盟庞。
3吃沪、刪除一個模塊,應當只影響有依賴關系的其他模塊什猖,而不應該影響其他無關部分票彪。
耦合性:
耦合:是對模塊間關聯程度的度量。耦合的強弱取決與模塊間接口的復雜性不狮、調用模塊的方式以及通過界面?zhèn)魉蛿祿亩嗌佟?/p>
? 模塊間的耦合度是指模塊之間的依賴關系降铸,包括控制關系、調用關系摇零、數據傳遞關系推掸。模塊間聯系越多,其耦合性越強遂黍,同時表明其獨立性越差终佛。降低模塊間的耦合度能減少模塊間的影響,防止對某一模塊修改所引起的“牽一發(fā)動全身”的水波效應雾家,保證系統(tǒng)設計順利進行铃彰。
? 耦合度就是某模塊(類)與其它模塊(類)之間的關聯、感知和依賴的程度芯咧,是衡量代碼獨立性的一個指標牙捉。
? 非直接耦合:兩個模塊之間沒有直接關系竹揍,它們之間的聯系完全是通過主模塊的控制和調用來實現的。耦合度最弱邪铲,模塊獨立性最強芬位。
? 數據耦合:調用模塊和被調用模塊之間只傳遞簡單的數據項參數。相當于高級語言中的值傳遞带到。
? 標記耦合:調用模塊和被調用模塊之間傳遞數據結構而不是簡單數據昧碉,同時也稱作特征耦合。表示和模塊間傳遞的不是簡單變量,而是像高級語言中的數據名、記錄名和文件名等數據結果医咨,這些名字即為標記,其實傳遞的是地址狭握。
? 控制耦合:模塊之間傳遞的不是數據信息,而是控制信息例如標志疯溺、開關量等论颅,一個模塊控制了另一個模塊的功能。
? 外部耦合:一組模塊都訪問同一全局簡單變量囱嫩,而且不通過參數表傳遞該全局變量的信息恃疯,則稱之為外部耦合。
? 公共耦合:一組模塊都訪問同一個全局數據結構挠说,則稱之為公共耦合澡谭。公共數據環(huán)境可以是全局數據結構、共享的通信區(qū)损俭、內存的公共覆蓋區(qū)等。如果模塊只是向公共數據環(huán)境輸入數據潘酗,或是只從公共數據環(huán)境取出數據杆兵,這屬于比較松散的公共耦合;如果模塊既向公共數據環(huán)境輸入數據又從公共數據環(huán)境取出數據仔夺,這屬于較緊密的公共耦合琐脏。
公共耦合會引起以下問題:
1. 無法控制各個模塊對公共數據的存取,嚴重影響了軟件模塊的可靠性和適應性缸兔。
2. 使軟件的可維護性變差日裙。若一個模塊修改了公共數據,則會影響相關模塊惰蜜。
3. 降低了軟件的可理解性昂拂。不容易清楚知道哪些數據被哪些模塊所共享,排錯困難抛猖。
? 一般地格侯,僅當模塊間共享的數據很多且通過參數傳遞很不方便時鼻听,才使用公共耦合。
內容耦合:一個模塊直接訪問另一模塊的內容联四,則稱這兩個模塊為內容耦合撑碴。
若在程序中出現下列情況之一,則說明兩個模塊之間發(fā)生了內容耦合:
1. 一個模塊直接訪問另一個模塊的內部數據朝墩。
2. 一個模塊不通過正常入口而直接轉入到另一個模塊的內部醉拓。
3. 兩個模塊有一部分代碼重疊(該部分代碼具有一定的獨立功能)。
4. 一個模塊有多個入口收苏。
? 內容耦合可能在匯編語言中出現亿卤。大多數高級語言都已設計成不允許出現內容耦合。這種耦合的耦合性最強倒戏,模塊獨立性最弱怠噪。
降低耦合度的方法:
1、少使用類的繼承杜跷,多用接口隱藏實現的細節(jié)傍念。 Java面向對象編程引入接口除了支持多態(tài)外, 隱藏實現細節(jié)也是其中一個目的葛闷。
2憋槐、模塊的功能化分盡可能的單一,道理也很簡單淑趾,功能單一的模塊供其它模塊調用的機會就少阳仔。(其實這是高內聚的一種說法,高內聚低耦合一般同時出現)扣泊。
3近范、遵循一個定義只在一個地方出現。
4延蟹、少使用全局變量评矩。
5、類屬性和方法的聲明少用public阱飘,多用private關鍵字斥杜。
6、多用設計模式沥匈,比如采用MVC的設計模式就可以降低界面與業(yè)務邏輯的耦合度蔗喂。
7、盡量不用“硬編碼”的方式寫程序高帖,同時也盡量避免直接用SQL語句操作數據庫缰儿。
8、最后當然就是避免直接操作或調用其它模塊或類(內容耦合)棋恼;如果模塊間必須存在耦合返弹,原則上盡量使用數據耦合锈玉,少用控制耦合,限制公共耦合的范圍义起,避免使用內容耦合拉背。
四:面向對象的三大基本特征(封裝、繼承和多態(tài))
一默终、封裝
? ?封裝從字面上來理解就是包裝的意思椅棺,專業(yè)點就是信息隱藏,是指利用抽象數據類型將數據和基于數據的操作封裝在一起齐蔽,使其構成一個不可分割的獨立實體两疚,數據被保護在抽象數據類型的內部,盡可能地隱藏內部的細節(jié)含滴,只保留一些對外接口使之與外部發(fā)生聯系诱渤,系統(tǒng)的其他對象只能通過包裹在數據外面的已經授權的操作來與這個封裝的對象進行交流和交互,也就是說用戶是無需知道對象內部的細節(jié)谈况,但可以通過該對象對外的提供的接口來訪問該對象勺美。
? ?對于封裝而言,一個對象它所封裝的是自己的屬性和方法碑韵,所以它是不需要依賴其他對象就可以完成自己的操作赡茸。
使用封裝有四大好處:
1、良好的封裝能夠減少耦合祝闻。
2占卧、類內部的結構可以自由修改。
3联喘、可以對成員進行更精確的控制华蜒。
4、隱藏信息實現細節(jié)豁遭。
? ?封裝把一個對象的屬性私有化友多,同時提供一些可以被外界訪問的屬性的方法,如果不想被外界方法堤框,我們大可不必提供方法給外界訪問。但是如果一個類沒有提供給外界訪問的方法纵柿,那么這個類也沒有什么意義了蜈抓。
? ?封裝可以使我們容易地修改類的內部實現,而無需修改使用了該類的客戶代碼昂儒。就可以對成員變量進行更精確的控制沟使。
二、繼承
2.1 繼承綜述:
? ?繼承是使用已存在的類的定義作為基礎建立新類的技術渊跋,新類的定義可以增加新的數據或新的功能腊嗡,也可以用父類的功能着倾,但不能選擇性地繼承父類。通過使用繼承我們能夠非常方便地復用以前的代碼燕少,能夠大大的提高開發(fā)的效率卡者。
? ?繼承所描述的是“is-a”的關系,如果有兩個對象A和B客们,若可以描述為“A是B”崇决,則可以表示A繼承B,其中B是被繼承者稱之為父類或者超類底挫,A是繼承者稱之為子類或者派生類恒傻。
? ?實際上繼承者是被繼承者的特殊化,它除了擁有被繼承者的特性外建邓,還擁有自己獨有得特性盈厘。例如貓有抓老鼠、爬樹等其他動物沒有的特性官边。同時在繼承關系中沸手,繼承者完全可以替換被繼承者,反之則不可以拒逮,例如我們可以說貓是動物罐氨,但不能說動物是貓就是這個道理,其實對于這個我們將其稱之為“向上轉型”滩援。
? ?繼承定義了類如何相互關聯栅隐,共享特性。對于若干個相同或者相似的類玩徊,我們可以抽象出他們共有的行為或者屬性并將其定義成一個父類或者超類租悄,然后用這些類繼承該父類,他們不僅可以擁有父類的屬性恩袱、方法還可以定義自己獨有的屬性或者方法把敢。
同時在使用繼承時需要記住三句話:
1桑阶、子類擁有父類非private的屬性和方法。
2椎椰、子類可以擁有自己屬性和方法,即子類可以對父類進行擴展篙骡。
3淘正、子類可以用自己的方式實現父類的方法黎泣。
? ?學習繼承一定少不了這三個東西:構造器项郊、protected關鍵字馅扣、向上轉型
2.2 構造器
? ?通過前面我們知道子類可以繼承父類的屬性和方法,除了那些private的外還有一樣是子類繼承不了的---構造器着降。對于構造器而言差油,它只能夠被調用,而不能被繼承任洞。 調用父類的構造方法我們使用super()即可蓄喇。
? ?構建過程是從父類“向外”擴散的,也就是從父類開始向子類一級一級地完成構建交掏。而且我們并沒有顯示的引用父類的構造器妆偏,這就是java的聰明之處:編譯器會默認給子類調用父類的構造器。
? ?但是盅弛,這個默認調用父類的構造器是有前提的:父類有默認構造器钱骂。如果父類沒有默認構造器,我們就要必須顯示的使用super()來調用父類構造器熊尉,否則編譯器會報錯:無法找到符合父類形式的構造器罐柳。
? ?對于子類而已,其構造器的正確初始化是非常重要的,而且當且僅當只有一個方法可以保證這點:在構造器中調用父類構造器來完成初始化,而父類構造器具有執(zhí)行父類初始化所需要的所有知識和能力狰住。
? ?對于繼承而言张吉,子類會默認調用父類的構造器,但是如果沒有默認的父類構造器催植,子類必須要顯示的指定父類的構造器肮蛹,而且必須是在子類構造器中做的第一件事(第一行代碼)。
2.3 protected關鍵字
? ?private訪問修飾符创南,對于封裝而言伦忠,是最好的選擇,但這個只是基于理想的世界稿辙,有時候我們需要這樣的需求:我們需要將某些事物盡可能地對這個世界隱藏昆码,但是仍然允許子類的成員來訪問它們。這個時候就需要使用到protected。
? ?對于protected而言赋咽,它指明就類用戶而言旧噪,他是private,但是對于任何繼承與此類的子類而言或者其他任何位于同一個包的類而言脓匿,他卻是可以訪問的淘钟。
2.4 向上轉型
? ?在上面的繼承中我們談到繼承是is-a的相互關系,貓繼承與動物陪毡,所以我們可以說貓是動物米母,或者說貓是動物的一種。這樣將貓看做動物就是向上轉型毡琉。
? ?將子類轉換成父類铁瞒,在繼承關系上面是向上移動的,所以一般稱之為向上轉型绊起。由于向上轉型是從一個叫專用類型向較通用類型轉換精拟,所以它總是安全的,唯一發(fā)生變化的可能就是屬性和方法的丟失虱歪。這就是為什么編譯器在“未曾明確表示轉型”或“未曾指定特殊標記”的情況下蜂绎,仍然允許向上轉型的原因。
2.5 謹慎繼承
? ?在這里我們需要明確笋鄙,繼承存在如下缺陷:
1师枣、父類變,子類就必須變萧落。
2践美、繼承破壞了封裝,對于父類而言找岖,它的實現細節(jié)對與子類來說都是透明的陨倡。
3、繼承是一種強耦合關系许布。
? ?所以說當我們使用繼承的時候兴革,我們需要確信使用繼承確實是有效可行的辦法。那么到底要不要使用繼承呢蜜唾?《Think in java》中提供了解決辦法:問一問自己是否需要從子類向父類進行向上轉型杂曲。如果必須向上轉型,則繼承是必要的袁余,但是如果不需要擎勘,則應當好好考慮自己是否需要繼承。
三颖榜、多態(tài)
3.1 多態(tài)綜述
? ?所謂多態(tài)就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發(fā)出的方法調用在編程時并不確定棚饵,而是在程序運行期間才確定煤裙,即一個引用變量到底會指向哪個類的實例對象,該引用變量發(fā)出的方法調用到底是哪個類中實現的方法蟹地,必須在由程序運行期間才能決定积暖。因為在程序運行時才確定具體的類,這樣怪与,不用修改源程序代碼,就可以讓引用變量綁定到各種不同的類實現上缅疟,從而導致該引用調用的具體方法隨之改變分别,即不修改程序代碼就可以改變程序運行時所綁定的具體代碼,讓程序可以選擇多個運行狀態(tài)存淫,這就是多態(tài)性耘斩。
? ?所以對于多態(tài)我們可以總結如下:
? ?指向子類的父類引用由于向上轉型了,它只能訪問父類中擁有的方法和屬性桅咆,而對于子類中存在而父類中不存在的方法括授,該引用是不能使用的,盡管是重載該方法岩饼。若子類重寫了父類中的某些方法荚虚,在調用該些方法的時候,必定是使用子類中定義的這些方法(動態(tài)連接籍茧、動態(tài)調用)版述。
? ?對于面向對象而言,多態(tài)分為編譯時多態(tài)和運行時多態(tài)寞冯。其中編輯時多態(tài)是靜態(tài)的渴析,主要是指方法的重載,它是根據參數列表的不同來區(qū)分不同的函數吮龄,通過編輯之后會變成兩個不同的函數俭茧,在運行時談不上多態(tài)。而運行時多態(tài)是動態(tài)的漓帚,它是通過動態(tài)綁定來實現的母债,也就是我們所說的多態(tài)性。
3.2 多態(tài)的實現條件
? ?在剛剛開始就提到了繼承在為多態(tài)的實現做了準備胰默。子類Child繼承父類Father场斑,我們可以編寫一個指向子類的父類類型引用,該引用既可以處理父類Father對象牵署,也可以處理子類Child對象铛铁,當相同的消息發(fā)送給子類或者父類對象時,該對象就會根據自己所屬的引用而執(zhí)行不同的行為惧磺,這就是多態(tài)均抽。即多態(tài)性就是相同的消息使得不同的類做出不同的響應挺据。
Java實現多態(tài)有三個必要條件:繼承、重寫脖隶、向上轉型扁耐。
繼承:在多態(tài)中必須存在有繼承關系的子類和父類。
重寫:子類對父類中某些方法進行重新定義产阱,在調用這些方法時就會調用子類的方法婉称。
向上轉型:在多態(tài)中需要將子類的引用賦給父類對象,只有這樣該引用才能夠具備技能調用父類的方法和子類的方法构蹬。
? ?只有滿足了上述三個條件王暗,我們才能夠在同一個繼承結構中使用統(tǒng)一的邏輯實現代碼處理不同的對象,從而達到執(zhí)行不同的行為庄敛。
? ?對于Java而言俗壹,它多態(tài)的實現機制遵循一個原則:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法藻烤,但是這個被調用的方法必須是在超類中定義過的绷雏,也就是說被子類覆蓋的方法。
3.3 實現形式???
? ?在Java中有兩種形式可以實現多態(tài):繼承和接口怖亭。?
3.2.1涎显、基于繼承實現的多態(tài) ????
? ?基于繼承的實現機制主要表現在父類和繼承該父類的一個或多個子類對某些方法的重寫,多個子類對同一方法的重寫可以表現出不同的行為依许。? ?
? ?基于繼承實現的多態(tài)可以總結如下:對于引用子類的父類類型棺禾,在處理該引用時,它適用于繼承該父類的所有子類峭跳,子類對象的不同膘婶,對方法的實現也就不同,執(zhí)行相同動作產生的行為也就不同蛀醉。 ????
? ?如果父類是抽象類悬襟,那么子類必須要實現父類中所有的抽象方法,這樣該父類所有的子類一定存在統(tǒng)一的對外接口拯刁,但其內部的具體實現可以各異脊岳。這樣我們就可以使用頂層類提供的統(tǒng)一接口來處理該層次的方法。 3.2.2垛玻、基于接口實現的多態(tài) ????
? ? 繼承是通過重寫父類的同一方法的幾個不同子類來體現的割捅,那么就可通過實現接口并覆蓋接口中同一方法的幾個不同的類體現的。 ????
? ? 在接口的多態(tài)中帚桩,指向接口的引用必須是指定這實現了該接口的一個類的實例程序亿驾,在運行時,根據對象引用的實際類型來執(zhí)行對應的方法账嚎。??
? ? 繼承都是單繼承莫瞬,只能為一組相關的類提供一致的服務接口儡蔓。但是接口可以是多繼承多實現,它能夠利用一組相關或者不相關的接口進行組合與擴充疼邀,能夠對外提供一致的服務接口喂江。所以它相對于繼承來說有更好的靈活性。
五:23種設計模式初探(創(chuàng)建型旁振,結構型获询,行為型)
一、設計模式的分類
創(chuàng)建型模式:這些設計模式提供了一種在創(chuàng)建對象的同時隱藏創(chuàng)建邏輯的方式拐袜,而不是使用 new 運算符直接實例化對象筐付。這使得程序在判斷針對某個給定實例需要創(chuàng)建哪些對象時更加靈活。?
? ?共五種:工廠模式(Factory Pattern)阻肿、抽象工廠模式(Abstract Factory Pattern)、單例模式(Singleton Pattern)沮尿、建造者模式(Builder Pattern)丛塌、原型模式(Prototype Pattern)?
結構型模式:這些設計模式關注類和對象的組合。繼承的概念被用來組合接口和定義組合對象獲得新功能的方式畜疾。?
? ?共七種:適配器模式(Adapter Pattern)赴邻、橋接模式(Bridge Pattern)、外觀模式(Facade Pattern)啡捶、組合模式(Composite Pattern)姥敛、裝飾器模式(Decorator Pattern)、享元模式(Flyweight Pattern)瞎暑、代理模式(Proxy Pattern)?
行為型模式:這些設計模式特別關注對象之間的通信彤敛。?
? ?共十一種:責任鏈模式(Chain of Responsibility Pattern)、命令模式(Command Pattern)了赌、解釋器模式(Interpreter Pattern)墨榄、迭代器模式(Iterator Pattern)、中介者模式(Mediator Pattern)勿她、備忘錄模式(Memento Pattern)袄秩、觀察者模式(Observer Pattern)、狀態(tài)模式(State Pattern)逢并、策略模式(Strategy Pattern)之剧、模板模式(Template Pattern)、訪問者模式(Visitor Pattern) ?
? ? 其實還有兩種模式:并發(fā)型模式和線程池模式砍聊。
模式一:工廠方法模式(Factory Method)
工廠方法模式分為三種: 普通工廠模式:就是建立一個工廠類背稼,對實現了同一接口的一些類進行實例的創(chuàng)建。?
類圖:
多個工廠方法模式:是對普通工廠方法模式的改進辩恼,在普通工廠方法模式中雇庙,如果傳遞的字符串出錯谓形,則不能正確創(chuàng)建對象,而多個工廠方法模式是提供多個工廠方法疆前,分別創(chuàng)建對象寒跳。
類圖:
? ? 靜態(tài)工廠方法模式:將上面的多個工廠方法模式里的方法置為靜態(tài)的,不需要創(chuàng)建實例竹椒,直接調用即可童太。????
? ? 總體來說,工廠模式適合:凡是出現了大量的產品需要創(chuàng)建胸完,并且具有共同的接口時书释,可以通過工廠方法模式進行創(chuàng)建。在以上的三種模式中赊窥,第一種如果傳入的字符串有誤爆惧,不能正確創(chuàng)建對象,第三種相對于第二種锨能,不需要實例化工廠類扯再,所以,大多數情況下址遇,我們會選用第三種——靜態(tài)工廠方法模式熄阻。
模式二:抽象工廠模式(Abstract Factory)????
? ? 工廠方法模式有一個問題就是,類的創(chuàng)建依賴工廠類倔约,也就是說秃殉,如果想要拓展程序,必須對工廠類進行修改浸剩,這違背了閉包原則钾军,所以,從設計角度考慮乒省,有一定的問題巧颈,如何解決?就用到抽象工廠模式袖扛,創(chuàng)建多個工廠類砸泛,這樣一旦需要增加新的功能,直接增加新的工廠類就可以了蛆封,不需要修改之前的代碼唇礁。 ????
? ? 其實這個模式的好處就是,如果你現在想增加一個功能:發(fā)及時信息惨篱,則只需做一個實現類盏筐,實現Sender接口,同時做一個工廠類砸讳,實現Provider接口琢融,就OK了界牡,無需去改動現成的代碼。這樣做漾抬,拓展性較好宿亡。?
?類圖:
模式三:單例模式(Singleton)????
? ? 單例對象(Singleton)是一種常用的設計模式。在Java應用中纳令,單例對象能保證在一個JVM中挽荠,該對象只有一個實例存在。
? ? 這樣的模式有幾個好處:?
1平绩、某些類創(chuàng)建比較頻繁圈匆,對于一些大型的對象,這是一筆很大的系統(tǒng)開銷捏雌。?
2跃赚、省去了new操作符,降低了系統(tǒng)內存的使用頻率性湿,減輕GC壓力来累。?
3、有些類如交易所的核心交易引擎窘奏,控制著交易流程,如果該類可以創(chuàng)建多個的話葫录,系統(tǒng)完全亂了着裹。(比如一個軍隊出現了多個司令員同時指揮,肯定會亂成一團)米同,所以只有使用單例模式骇扇,才能保證核心交易服務器獨立控制整個流程。 ?
單例模式的分類:?
1面粮、懶漢式單例模式?
2少孝、餓漢式單例模式?
3、雙重鎖單例模式:在Java指令中創(chuàng)建對象和賦值操作是分開進行的熬苍,也就是說instance = new Singleton();語句是分兩步執(zhí)行的稍走。但是JVM并不保證這兩個操作的先后順序,也就是說有可能JVM會為新的Singleton實例分配空間柴底,然后直接賦值給instance成員婿脸,然后再去初始化這個Singleton實例,這樣就可能出錯了柄驻。?
4狐树、靜態(tài)內部類單例模式:單例模式使用內部類來維護單例的實現,JVM內部的機制能夠保證當一個類被加載的時候鸿脓,這個類的加載過程是線程互斥的抑钟。這樣當我們第一次調用getInstance的時候涯曲,JVM能夠幫我們保證instance只被創(chuàng)建一次,并且會保證把賦值給instance的內存初始化完畢在塔,這樣我們就不用擔心上面的問題幻件。同時該方法也只會在第一次調用的時候使用互斥機制,這樣就解決了低性能問題心俗。
單例模式與靜態(tài)類的區(qū)別:
1傲武、靜態(tài)類不能實現接口,從類的角度說是可以的城榛,但是那樣就破壞了靜態(tài)了揪利。因為接口中不允許有static修飾的方法,所以即使實現了也是非靜態(tài)的狠持。?
2疟位、單例可以被延遲初始化,靜態(tài)類一般在第一次加載是初始化喘垂。之所以延遲加載甜刻,是因為有些類比較龐大,所以延遲加載有助于提升性能正勒。?
3得院、單例類可以被繼承,他的方法可以被覆寫章贞。但是靜態(tài)類內部方法都是static祥绞,無法被覆寫。?
4鸭限、單例類比較靈活蜕径,畢竟從實現上只是一個普通的Java類,只要滿足單例的基本需求败京,你可以在里面隨心所欲的實現一些其它功能兜喻,但是靜態(tài)類不行。從上面這些概括中赡麦,基本可以看出二者的區(qū)別朴皆,但是,從另一方面講泛粹,我們上面最后實現的那個單例模式车荔,內部就是用一個靜態(tài)類來實現的,所以戚扳,二者有很大的關聯忧便,只是我們考慮問題的層面不同罷了。
模式四:建造者模式(Builder)????
? ?工廠類模式提供的是創(chuàng)建單個類的模式,而建造者模式則是將各種產品集中起來進行管理珠增,用來創(chuàng)建復合對象超歌,所謂復合對象就是指某個類具有不同的屬性,其實建造者模式就是前面抽象工廠模式和最后的Test結合起來得到的蒂教。? ? ? ?
? ?建造者模式將很多功能集成到一個類里巍举,這個類可以創(chuàng)造出比較復雜的東西。所以與工程模式的區(qū)別就是:工廠模式關注的是創(chuàng)建單個產品凝垛,而建造者模式則關注創(chuàng)建符合對象的多個部分懊悯。因此,是選擇工廠模式還是建造者模式梦皮,依實際情況而定炭分。
模式五:原型模式(Prototype)????
? ? 原型模式雖然是創(chuàng)建型的模式,但是與工程模式沒有關系剑肯,從名字即可看出捧毛,該模式的思想就是將一個對象作為原型,對其進行復制让网、克隆呀忧,產生一個和原對象類似的新對象。
? ? 本小結會通過對象的復制溃睹,進行講解而账。在Java中,復制對象是通過clone()實現的因篇,先創(chuàng)建一個原型類:?
? ? public class Prototype implements Cloneable { ????
? ? public Object clone() throws CloneNotSupportedException { ????????
? ? Prototype proto = (Prototype) super.clone(); ???????
? ? return proto; ????}
?} ????
? ? 一個原型類福扬,只需要實現Cloneable接口,覆寫clone方法惜犀,此處clone方法可以改成任意的名稱,因為Cloneable接口是個空接口狠裹,你可以任意定義實現類的方法名虽界,如cloneA或者cloneB,因為此處的重點是super.clone()這句話涛菠,super.clone()調用的是Object的clone()方法莉御,而在Object類中,clone()是native的俗冻,具體怎么實現礁叔,我會在另一篇文章中,關于解讀Java中本地方法的調用迄薄,此處不再深究琅关。在這兒,我將結合對象的淺復制和深復制來說一下讥蔽,首先需要了解對象深涣易、淺復制的概念:
? ? 淺復制:將一個對象復制后画机,基本數據類型的變量都會重新創(chuàng)建,而引用類型新症,指向的還是原對象所指向的枪萄。?
? ? 深復制:將一個對象復制后问慎,不論是基本數據類型還有引用類型,都是重新創(chuàng)建的。簡單來說湘今,就是深復制進行了完全徹底的復制,而淺復制不徹底棚潦。 ????
? ? 要實現深復制儡率,需要采用流的形式讀入當前對象的二進制輸入,再寫出二進制數據對應的對象榛瓮。
????代碼示例:
????/* 淺復制 */
????public Object clone() throws CloneNotSupportedException {
????Prototype proto = (Prototype) super.clone();
????return proto;
????}
????/* 深復制 */
????public Object deepClone() throws IOException, ClassNotFoundException {
????/* 寫入當前對象的二進制流 */
????ByteArrayOutputStream bos = new ByteArrayOutputStream();
????ObjectOutputStream oos = new ObjectOutputStream(bos);
????oos.writeObject(this);
????/* 讀出二進制流產生的新對象 */
????ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
????ObjectInputStream ois = new ObjectInputStream(bis);
????return ois.readObject();
????}
結構型模式:
模式六:適配器模式(Adapter)
? ?適配器模式將某個類的接口轉換成客戶端期望的另一個接口表示铺董,目的是消除由于接口不匹配所造成的類的兼容性問題。主要分為三類:類的適配器模式禀晓、對象的適配器模式精续、接口的適配器模式。
? ?類的適配器模式:核心思想就是有一個Source類粹懒,擁有一個方法待適配重付,目標接口時Targetable,通過Adapter類凫乖,將Source的功能擴展到Targetable里确垫。
? ?應用場景:當希望將一個類轉換成滿足另一個新接口的類時,可以使用類的適配器模式帽芽,創(chuàng)建一個新類删掀,繼承原有的類导街,實現新的接口即可。
類圖:
????對象的適配器模式:基本思路和類的適配器模式相同泽论,只是將Adapter類作修改缚够,這次不繼承Source類潮瓶,而是持有Source類的實例陶冷,以達到解決兼容性的問題。
????應用場景:當希望將一個對象轉換成滿足另一個新接口的對象時毯辅,可以創(chuàng)建一個Wrapper類埂伦,持有原類的一個實例,在Wrapper類的方法中思恐,調用實例的方法就行沾谜。
????類圖:
? ? 接口的適配器模式:接口的適配器是這樣的:有時我們寫的一個接口中有多個抽象方法,當我們寫該接口的實現類時胀莹,必須實現該接口的所有方法基跑,這明顯有些比較浪費,因為并不是所有的方法都是我們需要的描焰,有時只需要某一些媳否,此處為了解決這個問題,我們引入了接口的適配器模式荆秦,借助于一個抽象類篱竭,該抽象類實現了該接口,實現了所有的方法步绸,而我們不和原始的接口打交道掺逼,只和該抽象類取得聯系,所以我們寫一個類瓤介,繼承該抽象類吕喘,重寫我們需要的方法就行。
? ?應用場景:接口的適配器模式:當不希望實現一個接口中所有的方法時刑桑,可以創(chuàng)建一個抽象類Wrapper氯质,實現所有方法,我們寫別的類的時候祠斧,繼承抽象類即可闻察。
????類圖:
模式七:裝飾模式(Decorator)
? 裝飾模式就是給一個對象增加一些新的功能,而且是動態(tài)的梁肿,要求裝飾對象和被裝飾對象實現同一個接口,裝飾對象持有被裝飾對象的實例觅彰。
? 裝飾器模式的應用場景:
1吩蔑、需要擴展一個類的功能。
2填抬、動態(tài)的為一個對象增加功能烛芬,而且還能動態(tài)撤銷,繼承不能做到這一點,繼承的功能是靜態(tài)的赘娄,不能動態(tài)增刪仆潮。
缺點:產生過多相似的對象,不易排錯遣臼。
類圖:
模式八:代理模式(Proxy)
? 代理模式就是多一個代理類出來性置,替原對象進行一些操作,比如我們在租房子的時候回去找中介揍堰,為什么呢鹏浅?因為你對該地區(qū)房屋的信息掌握的不夠全面,希望找一個更熟悉的人去幫你做屏歹,此處的代理就是這個意思隐砸。再如我們有的時候打官司,我們需要請律師蝙眶,因為律師在法律方面有專長季希,可以替我們進行操作,表達我們的想法幽纷。
應用場景:
如果已有的方法在使用的時候需要對原有的方法進行改進式塌,此時有兩種辦法:
1、修改原有的方法來適應霹崎。這樣違反了“對擴展開放珊搀,對修改關閉”的原則。
2尾菇、就是采用一個代理類調用原有的方法境析,且對產生的結果進行控制。這種方法就是代理模式派诬,使用代理模式劳淆,可以將功能劃分的更加清晰,有助于后期維護默赂。
類圖:
模式九:外觀模式(Facade)
? ?外觀模式是為了解決類與類之間的依賴關系沛鸵,外觀模式就是將他們的關系放在一個Facade類中,降低了類類之間的耦合度缆八,該模式中沒有涉及到接口曲掰。
? ?如果我們沒有Computer類,那么奈辰,CPU栏妖、Memory、Disk他們之間將會相互持有實例奖恰,產生關系吊趾,這樣會造成嚴重的依賴宛裕,修改一個類,可能會帶來其他類的修改论泛,這不是我們想要看到的揩尸,有了Computer類,他們之間的關系被放在了Computer類里屁奏,這樣就起到了解耦的作用岩榆,這就是外觀模式。
類圖:
模式十:橋接模式(Bridge)
? 橋接模式就是把事物和其具體實現分開了袁,使他們可以各自獨立的變化朗恳。橋接的用意是:將抽象化與實現化解耦,使得二者可以獨立變化载绿。
? 通過對Bridge類的調用粥诫,實現了對接口Sourceable的實現類SourceSub1和SourceSub2的調用。
類圖:
模式十一:組合模式(Composite)
? ?組合模式有時又叫部分-整體模式在處理類似樹形結構的問題時比較方便崭庸。
? ?使用場景:將多個對象組合在一起進行操作怀浆,常用于表示樹形結構中,例如二叉樹怕享,樹等执赡。
類圖:
模式十二:享元模式(Flyweight)
? ?享元模式的主要目的是實現對象的共享,即共享池函筋,當系統(tǒng)中對象多的時候可以減少內存的開銷沙合,通常與工廠模式一起使用。
? ?FlyWeightFactory負責創(chuàng)建和管理享元單元跌帐,當一個客戶端請求時首懈,工廠需要檢查當前對象池中是否有符合條件的對象,如果有谨敛,就返回已經存在的對象究履,如果沒有,則創(chuàng)建一個新對象脸狸,FlyWeight是超類最仑。一提到共享池,我們很容易聯想到Java里面的JDBC連接池炊甲,想想每個連接的特點泥彤,我們不難總結出:適用于作共享的一些個對象,他們有一些共有的屬性卿啡,就拿數據庫連接池來說吟吝,url、driverClassName牵囤、username爸黄、password及dbname,這些屬性對于每個連接來說都是一樣的揭鳞,所以就適合用享元模式來處理炕贵,建一個工廠類,將上述類似屬性作為內部數據野崇,其它的作為外部數據称开,在方法調用時,當做參數傳進來乓梨,這樣就節(jié)省了空間鳖轰,減少了實例的數量。
? ?通過連接池的管理扶镀,實現了數據庫連接的共享蕴侣,不需要每一次都重新創(chuàng)建連接,節(jié)省了數據庫重新創(chuàng)建的開銷臭觉,提升了系統(tǒng)的性能昆雀。
類圖:
行為型模式:
共11種:策略模式、模板方法模式蝠筑、觀察者模式狞膘、迭代子模式、責任鏈模式什乙、命令模式挽封、備忘錄模式、狀態(tài)模式臣镣、訪問者模式辅愿、中介者模式、解釋器模式退疫,共四類渠缕。
第一類:通過父類與子類的關系進行實現。
第二類:兩個類之間褒繁。
第三類:類的狀態(tài)亦鳞。
第四類:通過中間類。
模式十三:策略模式(strategy)
? ?策略模式定義了一系列算法棒坏,并將每個算法封裝起來燕差,使他們可以相互替換,且算法的變化不會影響到使用算法的客戶坝冕。需要設計一個接口徒探,為一系列實現類提供統(tǒng)一的方法,多個實現類實現該接口喂窟,設計一個抽象類(可有可無测暗,屬于輔助類)央串,提供輔助函數。
? ?策略模式的決定權在用戶碗啄,系統(tǒng)本身提供不同算法的實現质和,新增或者刪除算法,對各種算法做封裝稚字。因此饲宿,策略模式多用在算法決策系統(tǒng)中,外部用戶只需要決定用哪個算法即可胆描。
類圖:
模式十四:模板方法模式(Template Method)
? ?一個抽象類中瘫想,有一個主方法,再定義1...n個方法昌讲,可以是抽象的国夜,也可以是實際的方法,定義一個類短绸,繼承該抽象類支竹,重寫抽象方法,通過調用抽象類鸠按,實現對子類的調用礼搁。
類圖:
模式十五:觀察者模式(Observer)
? ?觀察者模式很好理解,類似于郵件訂閱和RSS訂閱目尖,當我們?yōu)g覽一些博客或wiki時馒吴,經常會看到RSS圖標,就這的意思是瑟曲,當你訂閱了該文章饮戳,如果后續(xù)有更新,會及時通知你洞拨。其實扯罐,簡單來講就一句話:當一個對象變化時,其它依賴該對象的對象都會收到通知烦衣,并且隨著變化歹河!對象之間是一種一對多的關系。
????類圖:
模式十六:迭代子模式(Iterator)
? ?迭代器模式就是順序訪問聚集中的對象花吟,一般來說秸歧,集合中非常常見,如果對集合類比較熟悉的話衅澈,理解本模式會十分輕松键菱。這句話包含兩層意思:一是需要遍歷的對象,即聚集對象今布,二是迭代器對象经备,用于對聚集對象進行遍歷訪問拭抬。
類圖:
模式十七:責任鏈模式(Chain of Responsibility)
? ?有多個對象,每個對象持有對下一個對象的引用侵蒙,這樣就會形成一條鏈玖喘,請求在這條鏈上傳遞,直到某一對象決定處理該請求蘑志。但是發(fā)出者并不清楚到底最終那個對象會處理該請求,所以贬派,責任鏈模式可以實現在隱瞞客戶端的情況下急但,對系統(tǒng)進行動態(tài)的調整。
? ?此處強調一點就是搞乏,鏈接上的請求可以是一條鏈波桩,可以是一個樹,還可以是一個環(huán)请敦,模式本身不約束這個镐躲,需要我們自己去實現,同時侍筛,在一個時刻萤皂,命令只允許由一個對象傳給另一個對象,而不允許傳給多個對象匣椰。
類圖:
模式十八:命令模式(Command)
? ?司令員下令讓士兵去干件事情裆熙,從整個事情的角度來考慮,司令員的作用是禽笑,發(fā)出口令入录,口令經過傳遞,傳到了士兵耳朵里佳镜,士兵去執(zhí)行僚稿。這個過程好在,三者相互解耦蟀伸,任何一方都不用去依賴其他人蚀同,只需要做好自己的事兒就行,司令員要的是結果啊掏,不會去關注到底士兵是怎么實現的唤崭。
? ?Invoker是調用者(司令員),Receiver是被調用者(士兵)脖律,MyCommand是命令谢肾,實現了Command接口,持有接收對象小泉。
? ?命令模式的目的就是達到命令的發(fā)出者和執(zhí)行者之間解耦芦疏,實現請求和執(zhí)行分開冕杠,熟悉Struts的同學應該知道,Struts其實就是一種將請求和呈現分離的技術酸茴,其中必然涉及命令模式的思想分预。
類圖:
模式十九:備忘錄模式(Memento)
? ?主要目的是保存一個對象的某個狀態(tài),以便在適當的時候恢復對象薪捍,個人覺得叫備份模式更形象些笼痹,通俗的講下:假設有原始類A,A中有各種屬性酪穿,A可以決定需要備份的屬性凳干,備忘錄類B是用來存儲A的一些內部狀態(tài),類C呢被济,就是一個用來存儲備忘錄的救赐,且只能存儲,不能修改等操作只磷。
? ?Original類是原始類经磅,里面有需要保存的屬性value及創(chuàng)建一個備忘錄類,用來保存value值钮追。Memento類是備忘錄類坪稽,Storage類是存儲備忘錄的類沈自,持有Memento類的實例畦韭,該模式很好理解底循。
類圖:
模式二十:狀態(tài)模式(State)
? ?當對象的狀態(tài)改變時,同時改變其行為惠毁,就拿QQ來說犹芹,有幾種狀態(tài),在線鞠绰、隱身腰埂、忙碌等,每個狀態(tài)對應不同的操作蜈膨,而且你的好友也能看到你的狀態(tài)屿笼,所以,狀態(tài)模式就兩點:
1翁巍、可以通過改變狀態(tài)來獲得不同的行為驴一。
2、你的好友能同時看到你的變化灶壶,狀態(tài)模式在日常開發(fā)中用的挺多的肝断,尤其是做網站的時候,我們有時希望根據對象的某一屬性,區(qū)別開他們的一些功能胸懈,比如說簡單的權限控制等担扑。
類圖:
模式二十一:訪問者模式(Visitor)
? ?訪問者模式把數據結構和作用于結構上的操作解耦合,使得操作集合可相對自由地演化趣钱。訪問者模式適用于數據結構相對穩(wěn)定算法又易變化的系統(tǒng)涌献。因為訪問者模式使得算法操作增加變得容易。若系統(tǒng)數據結構對象易于變化首有,經常有新的數據對象增加進來燕垃,則不適合使用訪問者模式。訪問者模式的優(yōu)點是增加操作很容易井联,因為增加操作意味著增加新的訪問者卜壕。訪問者模式將有關行為集中到一個訪問者對象中,其改變不影響系統(tǒng)數據結構低矮。其缺點就是增加新的數據結構很困難。
? ?簡單來說被冒,訪問者模式就是一種分離對象數據結構與行為的方法军掂,通過這種分離,可達到為一個被訪問者動態(tài)添加新的操作而無需做其它的修改的效果昨悼。
? ?適用場景:如果我們想為一個現有的類增加新功能蝗锥,不得不考慮幾個事情:
1、新功能會不會與現有功能出現兼容性問題率触?
2终议、以后會不會再需要添加?
3葱蝗、如果類不允許修改代碼怎么辦穴张?
? ?面對這些問題,最好的解決方法就是使用訪問者模式两曼,訪問者模式適用于數據結構相對穩(wěn)定的系統(tǒng)皂甘,把數據結構和算法解耦。
類圖:
模式二十二:中介者模式(Mediator)
? ?中介者模式也是用來降低類類之間的耦合的悼凑,因為如果類類之間有依賴關系的話偿枕,不利于功能的拓展和維護,因為只要修改一個對象户辫,其它關聯的對象都得進行修改渐夸。如果使用中介者模式,只需關心和Mediator類的關系渔欢,具體類類之間的關系及調度交給Mediator就行墓塌。
類圖:
模式二十三:解釋器模式(Interpreter)
? ?解釋器模式是我們暫時的最后一講,一般主要應用在OOP開發(fā)中的編譯器的開發(fā)中,解釋器模式用來做各種各樣的解釋器桃纯,如正則表達式等的解釋器等酷誓,所以適用面比較窄。
類圖:
六:面向對象的七大設計原則
原則一:開閉原則(Open-Closed Principle, OCP)
定義:軟件實體應當對擴展開放态坦,對修改關閉盐数。這句話說得有點專業(yè),更通俗一點講伞梯,也就是:軟件系統(tǒng)中包含的各種組件玫氢,例如模塊(Modules)、類(Classes)以及功能(Functions)等等谜诫,應該在不修改現有代碼的基礎上漾峡,去擴展新功能。開閉原則中原有“開”喻旷,是指對于組件功能的擴展是開放的生逸,是允許對其進行功能擴展的;開閉原則中“閉”且预,是指對于代碼的修改是封閉的槽袄,即不應該修改原有的代碼。
????問題由來:凡事的產生都有緣由锋谐。我們來看看遍尺,開閉原則的產生緣由。在軟件的生命周期內涮拗,因為變化乾戏、升級和維護等原因需要對軟件原有代碼進行修改時,可能會給舊代碼中引入錯誤三热,也可能會使我們不得不對整個功能進行重構鼓择,并且需要原有代碼經過重新測試。這就對我們的整個系統(tǒng)的影響特別大就漾,這也充分展現出了系統(tǒng)的耦合性如果太高惯退,會大大的增加后期的擴展,維護从藤。為了解決這個問題催跪,故人們總結出了開閉原則。解決開閉原則的根本其實還是在解耦合夷野。所以懊蒸,我們面向對象的開發(fā),我們最根本的任務就是解耦合悯搔。
????解決方法:當軟件需要變化時骑丸,盡量通過擴展軟件實體的行為來實現變化,而不是通過修改已有的代碼來實現變化。
????小結:開閉原則具有理想主義的色彩通危,說的很抽象铸豁,它是面向對象設計的終極目標。其他幾條原則菊碟,則可以看做是開閉原則的實現节芥。我們要用抽象構建框架,用實現擴展細節(jié)逆害。
原則二:單一職責原則(Single Responsibility Principle)
定義:一個類头镊,只有一個引起它變化的原因。即:應該只有一個職責魄幕,每一個職責都是變化的一個軸線相艇,如果一個類有一個以上的職責,這些職責就耦合在了一起纯陨。這會導致脆弱的設計坛芽。當一個職責發(fā)生變化時,可能會影響其它的職責翼抠。另外咙轩,多個職責耦合在一起,會影響復用性机久。例如:要實現邏輯和界面的分離臭墨。需要說明的一點是單一職責原則不只是面向對象編程思想所特有的赔嚎,只要是模塊化的程序設計膘盖,都需要遵循這一重要原則。
? ?問題由來:類T負責兩個不同的職責:職責P1尤误,職責P2侠畔。當由于職責P1需求發(fā)生改變而需要修改類T時,有可能會導致原本運行正常的職責P2功能發(fā)生故障损晤。
? ?解決方法:分別建立兩個類T1软棺、T2,使T1完成職責P1功能尤勋,T2完成職責P2功能喘落。這樣,當修改類T1時最冰,不會使職責P2發(fā)生故障風險瘦棋;同理,當修改T2時暖哨,也不會使職責P1發(fā)生故障風險赌朋。
原則三:里氏替換原則(Liskov Substitution Principle)
定義:子類型必須能夠替換掉它們的父類型。注意這里的能夠兩字。有人也戲稱老鼠的兒子會打洞原則沛慢。
? ?問題由來:有一功能P1赡若,由類A完成∮舛現需要將功能P1進行擴展伐庭,擴展后的功能為P,其中P由原有功能P1與新功能P2組成霸株。新功能P由類A的子類B來完成,則子類B在完成新功能P2的同時集乔,有可能會導致原有功能P1發(fā)生故障扰路。
? ?解決方法:類B繼承類A時汗唱,除添加新的方法完成新增功能P2外哩罪,盡量不要重寫父類A的方法际插,也盡量不要重載父類A的方法辛辨。
? ?小結:所有引用父類的地方必須能透明地使用其子類的對象斗搞。子類可以擴展父類的功能,但不能改變父類原有的功能溅呢,即:子類可以實現父類的抽象方法驶鹉,子類中也可以增加自己特有的方法室埋,但不能覆蓋父類的非抽象方法姚淆。當子類的方法重載父類的方法時腌逢,方法的前置條件(即方法的形參)要比父類方法的輸入參數更寬松。當子類的方法實現父類的抽象方法時媒惕,方法的后置條件(即方法的返回值)要比父類更嚴格妒蔚。
原則四:迪米特法則(Law Of Demeter)
定義:迪米特法則又叫最少知道原則,即:一個對象應該對其他對象保持最少的了解叁鉴。如果兩個類不必彼此直接通信,那么這兩個類就不應當發(fā)生直接的相互作用。如果其中一個類需要調用另一個類的某一個方法的話弹渔,可以通過第三者轉發(fā)這個調用肢专。簡單定義只為與直接的朋友通信椿胯。首先來解釋一下什么是直接的朋友:每個對象都會與其他對象有耦合關系哩盲,只要兩個對象之間有耦合關系,我們就說這兩個對象之間是朋友關系抒线。耦合的方式很多十兢,依賴、關聯宵呛、組合、聚合等逮矛。其中须鼎,我們稱出現成員變量、方法參數赡译、方法返回值中的類為直接的朋友裹唆,而出現在局部變量中的類則不是直接的朋友品腹。也就是說,陌生的類最好不要作為局部變量的形式出現在類的內部羡鸥。
? ?問題由來:類與類之間的關系越密切,耦合度越大衷旅,當一個類發(fā)生改變時柿顶,對另一個類的影響也越大,最早是在1987年由美國Northeastern University的Ian Holland提出家乘。通俗的來講,就是一個類對自己依賴的類知道的越少越好业崖。也就是說复罐,對于被依賴的類來說胀滚,無論邏輯多么復雜咽笼,都盡量地的將邏輯封裝在類的內部媳纬,對外除了提供的public方法钮惠,不對外泄漏任何信息。迪米特法則還有一個更簡單的定義:只與直接的朋友通信。
? ?解決方法:盡量降低類與類之間的耦合撰糠。 自從我們接觸編程開始,就知道了軟件編程的總的原則:低耦合遮斥,高內聚。無論是面向過程編程還是面向對象編程较屿,只有使各個模塊之間的耦合盡量的低,才能提高代碼的復用率嘱么。
? ?迪米特法則的初衷是降低類之間的耦合几迄,由于每個類都減少了不必要的依賴,因此的確可以降低耦合關系解孙。但是凡事都有度,雖然可以避免與非直接的類通信娱据,但是要通信,必然會通過一個“中介”來發(fā)生聯系。故過分的使用迪米特原則郊愧,會產生大量這樣的中介和傳遞類,導致系統(tǒng)復雜度變大焦蘑。所以在采用迪米特法則時要反復權衡,既做到結構清晰,又要高內聚低耦合腋腮。
原則五:依賴倒置原則(Dependence Inversion Principle)
定義:高層模塊不應該依賴低層模塊详羡,二者都應該依賴其抽象水泉;抽象不應該依賴細節(jié)钢拧;細節(jié)應該依賴抽象,中心思想是面向接口編程膜钓。
? ?問題由來:類A直接依賴類B,假如要將類A改為依賴類C沃疮,則必須通過修改類A的代碼來達成。這種場景下,類A一般是高層模塊吨些,負責復雜的業(yè)務邏輯;類B和類C是低層模塊,負責基本的原子操作屏轰;假如修改類A姆吭,會給程序帶來不必要的風險。
? ?解決方法:將類A修改為依賴接口I,類B和類C各自實現接口I昂灵,類A通過接口I間接與類B或者類C發(fā)生聯系鹏氧,則會大大降低修改類A的幾率实蓬。
在實際編程中,我們一般需要做到如下3點:
1酌伊、低層模塊盡量都要有抽象類或接口,或者兩者都有。
2咒彤、變量的聲明類型盡量是抽象類或接口。
3歇拆、使用繼承時遵循里氏替換原則谐区。
采用依賴倒置原則尤其給多人合作開發(fā)帶來了極大的便利昭抒,參與協作開發(fā)的人越多坤邪、項目越龐大,采用依賴倒置原則的意義就越重大艇纺。
小結:依賴倒置原則就是要我們面向接口編程怎静,理解了面向接口編程,也就理解了依賴倒置黔衡。
原則六:接口隔離原則(Interface Segregation Principle)
定義:客戶端不應該依賴它不需要的接口蚓聘;一個類對另一個類的依賴應該建立在最小的接口上盟劫。
問題由來:類A通過接口I依賴類B夜牡,類C通過接口I依賴類D,如果接口I對于類A和類B來說不是最小接口侣签,則類B和類D必須去實現他們不需要的方法
解決方法:
1塘装、使用委托分離接口。
2影所、使用多重繼承分離接口蹦肴。
3、將臃腫的接口I拆分為獨立的幾個接口型檀,類A和類C分別與他們需要的接口建立依賴關系冗尤,也就是采用接口隔離原則。
小結:我們在代碼編寫過程中,運用接口隔離原則裂七,一定要適度皆看,接口設計的過大或過小都不好。對接口進行細化可以提高程序設計靈活性是不爭的事實背零,但是如果過小腰吟,則會造成接口數量過多,使設計復雜化徙瓶。所以一定要適度毛雇。設計接口的時候,只有多花些時間去思考和籌劃侦镇,就能準確地實踐這一原則灵疮。
原則七:合成/聚合原則(Composite/Aggregate Reuse Principle,CARP)
定義:也有人叫做合成復用原則,及盡量使用合成/聚合壳繁,盡量不要使用類繼承震捣。換句話說,就是能用合成/聚合的地方闹炉,絕不用繼承蒿赢。
為什么要盡量使用合成/聚合而不使用類繼承?
1渣触、對象的繼承關系在編譯時就定義好了羡棵,所以無法在運行時改變從父類繼承的子類的實現。
2嗅钻、子類的實現和它的父類有非常緊密的依賴關系皂冰,以至于父類實現中的任何變化必然會導致子類發(fā)生變化。
3啊犬、當你復用子類的時候灼擂,如果繼承下來的實現不適合解決新的問題,則父類必須重寫或者被其它更適合的類所替換觉至,這種依賴關系限制了靈活性剔应,并最終限制了復用性。
總結:這些原則在設計模式中體現的淋淋盡致语御,設計模式就是實現了這些原則峻贮,從而達到了代碼復用、增強了系統(tǒng)的擴展性应闯。所以設計模式被很多人奉為經典纤控。我們可以通過好好的研究設計模式,來慢慢的體會這些設計原則碉纺。
七:重構的注意事項與技術實現(重構-改善既有代碼的設計)
代碼的壞味道:
1船万、重復的代碼
? ?重復的代碼是壞味道中出現頻率最高的情形非其莫屬刻撒。如果在一個的以上地方看到相同的代碼,那么就可以肯定:想辦法將它們合而為一耿导,代碼會變得更好声怔。最單純的重復代碼就是“同一個類的兩個函數含有相同的表達式”,這時候可以采用抽取方法提煉出重復的代碼舱呻,然后讓這兩個地點都調用被提煉出的那一段代碼醋火。
? ?另一種常見情況就是“兩個互為兄弟的子類內含相同的表達式”,這時候只需對兩個類抽取方法箱吕,然后將提煉出的代碼推入到超類中芥驳。如果代碼之間只是類似而并非完全相同,那么就需要通過抽取方法將相似部分和差異部分分開茬高,構成單獨一個函數歹叮。如果有些函數以不同的算法做相同的事想邦,可以使用比較清晰的一個替換掉其余的近尚。
2客燕、過長的函數
? ?程序員都喜歡簡短的函數慨亲。擁有短函數的對象會活的比較好婚瓜、比較長。不熟悉面向對象技術的人刑棵,常常覺得對象程序中只有無窮無盡的委托巴刻,根本沒有進行任何計算。和此類程序共同生活數年后蛉签,你才會知道這些小小函數的價值胡陪。
? ?應該積極地分解函數,將長長的函數變?yōu)槎鄠€短小的函數碍舍。一般會遵循這樣的原則:每當感覺需要用注釋來說明點什么的時候柠座,就把需要說明的東西寫進一個獨立函數中,并以其用途命名片橡。不要嫌麻煩妈经。可以對一組甚至短短一行代碼做這件事捧书,哪怕替換后的函數調用動作比函數自身還長吹泡,只要函數名稱能夠解釋其用途,也應毫不猶豫地那么做经瓷。關鍵不在于函數的長度爆哑,而在于函數“做什么”和“如何做”之間的語義距離。
3舆吮、過大的類
? ?如果想利用單個的類做太多的事情揭朝,其內往往會出現太多實例變量队贱。一旦如此,重復的代碼就接踵而來潭袱。
? ?可以將幾個變量一起提煉至新類內露筒。提煉時應該選擇類內彼此相關的變量,將它們放在一起敌卓。通常如果類內的數個變量有著相同的前綴或字尾慎式,這就意味有機會把它們提煉到某個組件內。
? ?和“太多實例變量”一樣趟径,類內如果有太多代碼瘪吏,也是代碼重復、混亂并最終走向死亡的源頭蜗巧。最簡單的方案是把多余的東西消弭于類內部掌眠。如果有五個“百行函數”,它們之中很多代碼都相同幕屹,那么或許你可以把它們變成五個“十行函數”和十個提煉出的“雙行函數”蓝丙。
4、過長的參數列
? ?剛開始學編程的時候望拖,或許都是“把函數所需的所有東西都以參數傳遞進去”渺尘。這樣也是可以理解的,因為除此之外就只能選擇全局數據说敏,而全局數據是邪惡的東西鸥跟。對象技術告訴我們,如果你手上沒有所需的東西盔沫,總可以叫一個對象給你医咨。有了對象,你就不必要把函數所需的所有東西都以參數傳遞給它架诞,只需傳給它足夠的拟淮、讓函數能從中獲得自己的東西就行。
? ?太長的的參數列難以理解谴忧,太多參數會造成前后不一致很泊、不易使用,而且一旦需要更多數據俏蛮,就不得不修改它撑蚌。如果將對象傳遞給函數,大多數修改都將沒有必要搏屑,因為很可能只需增加一兩條請求争涌,就能得到更多的數據。
5辣恋、發(fā)散式變化
? ?我們希望軟件能夠容易被修改——畢竟軟件再怎么說本來就該是“軟”的亮垫。一旦需要修改模软,我們希望能夠跳到系統(tǒng)某一點,只在該處做修改饮潦。如果不能做到這點燃异,你就會嗅出兩種緊密相關的刺鼻味道中的一種。
? ?如果某個類經常因為不同的原因在不同的方向上發(fā)生變化继蜡,發(fā)散式變化就出現了回俐。其主要指“一個類受多種變化的影響”。當你看著一個類說:“呃稀并,如果新加入一個數據庫仅颇,就必須修改這三個函數;如果新出現一種工具碘举,就必須修改這四個函數忘瓦。”那么此時也許將這個對象分成兩個會更好引颈,這樣對每個對象就可以只因一種變化而需要修改耕皮。
6、霾彈式修改
? ?如果每遇到變化蝙场,都必須在許多不同的類內做出許多小修改凌停,你所面臨的壞味道就是霾彈式修改。其主要指“一種變化引發(fā)多個類相應修改”李丰。如果需要修改的代碼散布四周苦锨,不但很難找到它們,也很容易忘記某個重要的修改趴泌。
????這種情況可以把所有需要的代碼放進同一個類。如果眼下沒有合適的類可以安置這些代碼拉庶,就創(chuàng)造一個嗜憔。通常可以運用內聯類把一系列相關行為放進同一個類氏仗。
7吉捶、依戀情節(jié)
? ?眾所周知,對象技術的全部要點在于:其是一種“將數據和對數據的操作行為包裝在一起”的技術皆尔。有一種經典的氣味:函數對于某個類的興趣高過對自己所處類的興趣呐舔。在很多情況下,都能夠看到:某個函數為了計算某個值慷蠕,從另一個對象那兒調用幾乎半打的取值函數珊拼。療法也顯而易見:把這個函數移至另一個地點,移到它該去的地方流炕∨煜郑‘
????有時候一個函數往往會用到幾個類的功能仅胞,那么它究竟該被置于何處呢?處理原則通常為:判斷哪個類擁有最多被此函數使用的數據剑辫,然后就把這個函數和那些數據擺在一起干旧。
8、數據泥團
? ?如果用比較形象的事物來形容數據項妹蔽,我想“小孩子”是一個不錯的選擇椎眯,數據項就像小孩子,喜歡成群結隊地呆在一塊兒胳岂。常持咽樱可以在很多地方看到相同的三四項數據:兩個類中相同的字段、許多函數簽名中相同的參數旦万。這些總是綁在一起出現的數據真應該擁有屬于它們自己的對象闹击。
? ?這種情況可以先找出這些數據以字段形式出現的地方,將它們提煉到一個獨立對象中成艘,然后將注意力轉移到函數簽名上赏半,運用參數對象為它減肥。這樣做的直接好處是可以將很多參數列縮短淆两,簡化函數調用断箫。一個比較好的評判方法是:刪掉眾多數據中的一項。這么做其它數據有沒有因而失去意義秋冰?如果它們不再有意義仲义,這就是一個明確的信號:應該為它們產生一個新對象。
9剑勾、基本類型偏執(zhí)
? ?大多數編程環(huán)境都有兩種數據:結構類型允許你將數據組織成有意義的形式埃撵;基本類型則是構成結構類型的積木塊。但是請記姿淞怼:結構總是會帶來一定的額外開銷暂刘。它們可能代表著數據庫中的表,如果只為做一兩件事而創(chuàng)建結構類型也可能顯得很麻煩捂刺。
? ?對象的一個極大價值在于:它們模糊甚至打破橫亙于基本數據和體積較大的類之間的界限谣拣。如果你有一組應該總是被放在一起的字段,可以將其抽取為一個獨立的類族展。如果你在參數列中看到基本型數據森缠,可以引入參數對象進行處理。如果你發(fā)現自己正從數組中挑選數據仪缸,可以運用以對象取代數組進行處理贵涵。
10、Switch驚悚現身
? ?面向對象程序的一個較明顯特征是:少用switch語句。從本質上說独悴,switch語句的問題在于重復例书。你常會發(fā)現同樣的switch語句散布于不同的地方。如果要為它添加一個新的case語句刻炒,就必須找到所用switch語句并修改它們决采。面向對象中的多態(tài)概念可為此帶來優(yōu)雅的解決辦法。
? ?大多數時候坟奥,一看到switch語句树瞭,那就應該考慮以多態(tài)來替換它。switch語句常常根據類型碼進行選擇爱谁,你要的是“與該類型碼相關的函數或類”晒喷,所以應該將switch語句提煉到一個獨立函數中,再將它搬移到需要多態(tài)性的那個類里访敌。
11凉敲、平行繼承體系
? ?平行繼承體系其實是霾彈式修改的特殊情況。在這種情況下寺旺,每當為某個類增加一個子類爷抓,必須也為另一個類增加一個子類。如果發(fā)現某個繼承體系的類名稱前綴和另一個繼承體系的類名稱前綴完全相同阻塑,這種壞味道就會被嗅出蓝撇。
? ?消除這種重復性的一般策略是:讓一個繼承體系的實例引用另一個繼承體系的實例。
12陈莽、冗贅類
? ?你所創(chuàng)建的每一個類渤昌,都得有人去理解它、維護它走搁,這些工作都是需要花錢的独柑。如果一個類的所得并不值其身價,他就應該消失朱盐。項目中經常會出現這樣的情況:某個類原本對得起自己的價值群嗤,但重構使它身形縮水,不再做那么多工作兵琳;或開發(fā)者事先規(guī)劃了某些變化,并添加一個類來應付這些變化骇径,但變化實際沒有發(fā)生躯肌。
? ?不管是哪種原因,都應該讓這個類莊嚴赴義吧破衔。如果某些子類并沒有做足夠的工作清女,我們可以嘗試“折疊繼承體系”,將超類和子類合為一體晰筛,那樣就會減少維護時間嫡丙。對于那些幾乎沒用的組件拴袭,就應該將這個類的所有特性搬移到另一個類中,然后移除原類曙博。
13拥刻、夸夸其談未來性
? ?我們經常會說:“我想總有一天需要做這事”,并因而企圖以各樣的鉤子和特殊情況來處理一些非必要的事情父泳。一旦這樣般哼,壞味道就浮現出來了』菡夸夸其談未來的結果往往會造成系統(tǒng)更加難以理解和維護蒸眠。如果所有的裝置都被用到了,那就值得那么做杆融;如果用不到楞卡,就不值得。用不上的裝置只會阻擋你的路脾歇,給你添亂蒋腮,那就搬開它吧。
? ?如果某個抽象類其實沒有太大作用介劫,可以將超類和子類合為一體徽惋。將不必要的委托轉移到另一個類中,并消除原先的類座韵。如果函數的某些參數未被用上险绘,那么就將參數移走。如果函數名稱帶有多余的抽象意味誉碴,就應該對它重命名宦棺,讓它現實一些。
14黔帕、令人迷惑的暫時字段
? ?有時候你會發(fā)現:類中的某個實例變量僅為某種特定情況而設代咸。這樣的代碼讓人難以理解,因為你通常認為對象在所有時候都需要它的所有變量成黄。當變量在未被使用的情況下去猜測其當初設置的目的呐芥,會讓你發(fā)瘋的。
? ?可以使用提煉新類為這個可憐的孤兒創(chuàng)造一個家奋岁,然后把所有和這個變量相關的代碼都放進這個新家思瘟。
? ?也許還可以使用“將Null值替換為Null對象”在“變量不合法”的情況下創(chuàng)建一個Null對象,從而避免寫出條件式代碼闻伶。
15滨攻、過度耦合的消息鏈
? ?如果你看到用戶向一個對象請求另一個對象,然后再向后者請求另一個對象,然后再請求另一個對象.....這就是消息鏈光绕。這種方式意味著客戶代碼將與某些功能函數中的導航結構緊密耦合女嘲。一旦對象間的關系發(fā)生任何變化,客戶端就不得不做出相應修改诞帐。
? ?這時候我們可以隱藏“委托關系”欣尼,并在服務類上建立客戶所需要的所有函數。你可以在消息鏈的不同位置進行這種重構手法景埃。理論上是可以重構消息鏈上的任何一個對象媒至,但是這樣做往往會把一系列對象都變成“中間人”。通常更好的選擇是:先觀察消息鏈最終得到的對象是用來干什么的谷徙,再看看能否通過抽取方法把使用該對象的代碼提煉到一個獨立函數中拒啰,然后再將這個函數推入消息鏈。
16完慧、中間人
? ?我們都知道對象的基本特征之一就是封裝——對外部世界隱藏其內部細節(jié)谋旦。封裝往往伴隨著委托。比如你對Boss說是否有時間參加一個會議屈尼,他把這個消息“委托”給他的記事本册着,然后才能回答你。但是脾歧,你沒有必要知道Boss到底使用傳統(tǒng)記事本或電子記事本亦或秘書來記錄自己的約會甲捏。
? ?人們可能會過度使用委托。你也許會看到某個類接口中有一半的函數都委托給其它類鞭执,這樣就是過度委托司顿。這時候就應該移除中間人,直接和真正的負責人打交道兄纺。如果這樣“不干實事”的函數只有少數幾個大溜,可以將它們放進調用端。如果中間人還有其它行為估脆,可以把它變成實責對象的子類钦奋,這樣你既可以擴展原對象的行為,又不必負擔那么多的委托動作疙赠。
17付材、狎昵關系
? ?有時候你會看到兩個類過于親密,花費太多時間去探究彼此的private成分圃阳。如果這發(fā)生在兩個“人”之間伞租,我們無比做衛(wèi)道士;但對于類限佩,我們就希望它們嚴守清規(guī)。
? ?也許就像古代的戀人一樣,過分狎昵的類必須拆散祟同∽鞔可以通過“移動方法”和“移動字段”幫它們劃清界限,從而減少狎昵行徑晕城。如果兩個類實在是情投意合泞坦,可以把兩者共同點提煉到一個安全地點,讓它們坦蕩地使用這個新類砖顷》∷或者通過隱藏“委托關系”讓另一個類來為它們傳遞相思情。
18滤蝠、異曲同工的類
? ?如果兩個函數做同一件事豌熄,卻有著不同的簽名,可以根據它們的用途重新命名物咳。
? ?但這往往不夠锣险,可以反復將某些行為移入類中,直到兩者的協議一致為止览闰。
19芯肤、不完美的庫類
? ?復用常被視為對象的終極目的。不過我們認為復用的意義經常被高估——大多數對象只要夠用就好压鉴。但是無可否認崖咨,許多編程技術都建立在程序庫的基礎上。庫類構建者沒有未卜先知的能力油吭,我們不能因此責怪它們击蹲。
? ?幸好我們有兩個專門應付這種情況的工具。如果你只想修改庫類的一兩個函數上鞠,可以使用“引入外加參數”來進行修改际邻。如果想要添加一大堆額外行為,就得運用“引入本地擴展(建立一個新類芍阎,使它包含這些額外函數世曾。讓這個擴展品成為源類的子類或包裝類)”來進行修改。
20谴咸、純稚的數據類
? ?純稚的數據類是指:它們擁有一些字段轮听,以及用于訪問(讀寫)這些字段的函數,除此之外一無長物岭佳。這樣的類只是一種不會說話的數據容器血巍,它們幾乎一定被其它類過分細瑣地操控著。
? ?這些類早期可能擁有public字段珊随,果真如此就應該在別人注意到它們之前將它們封裝起來述寡。如果這些類內含容器類的字段柿隙,就應該檢查它們是不是得到了恰當的封裝;如果沒有鲫凶,就把它們封裝起來禀崖。對于那些不該被其它類修改的字段,就應該去掉該字段的所有設值函數螟炫。
21波附、被拒絕的遺贈
? ?子類應該繼承超類的函數和數據。但是如果它們不想或者不需要繼承昼钻,又該怎么辦呢掸屡?它們得到了所有的禮物,但卻只從中挑選幾樣來玩然评!
? ?這可能意味著繼承體系設計錯誤仅财。你需要為這個子類新建一個兄弟類,再把所有用不到的函數下推給那個兄弟沾瓦。這樣一來满着,超類就只持有所有子類共享的東西。你常常會聽到這樣的建議:所有超類都應該是抽象的贯莺。
22风喇、過多的注釋
? ?不要擔心,并不是說不應該寫注釋缕探。從嗅覺上說魂莫,注釋不是一種壞味道,事實上它還是一種香味呢爹耗。常常還有這樣的情況:你看到一段代碼有著長長的注釋耙考,然后發(fā)現,這些注釋之所以存在乃是因為代碼很糟糕潭兽。這種情況出現次數多的實在令人吃驚倦始。
? ?注釋可以帶我們找到本文先前提到的各種壞味道。找到壞味道后山卦,我們首先應該以各種重構手法把壞味道去除鞋邑。完成之后我們發(fā)現注釋已經變得多余了,因為代碼已經清楚說明了一切账蓉。
? ?如果你需要注釋來解釋一塊代碼做了什么枚碗,試試將該代碼抽取為一個單獨的函數东揣;如果函數已經被提煉出來志衣,但還是需要注釋來解釋其行為羡玛,試著對其重新命名牌柄。當你感覺需要撰寫注釋時,請先嘗試重構讯檐,試著讓所有注釋都變得多余俭茧。
八:阿里巴巴代碼規(guī)范插件的使用
如何安裝:
使用結果:
九:擴展閱讀
1弧烤、https://alitech-private.oss-cn-beijing.aliyuncs.com/1519806643286/Android1_0_0.pdf?Expires=1613881214&OSSAccessKeyId=LTAI4G7JAotCoNVvbmrLZNtj&Signature=9qfFDk%2BMgQkjtBZqZKIrRlPzCCE%3D(阿里巴巴Android開發(fā)手冊)
2、http://www.reibang.com/p/b7a644ea0d25(Android你不能忽略的代碼命名規(guī)范)
4限番、書籍:重構-改善既有代碼的設計
5、Android插件:Alibaba Java Coding Guidelines
6呀舔、https://www.cnblogs.com/zsychanpin/p/6871668.html(22 種代碼的壞味道)
7、https://www.cnblogs.com/Braveliu/p/7388503.html(重構列表)
8扩灯、https://blog.csdn.net/qq_41331645/article/details/81102015(面向對象之七大原則)
9媚赖、https://blog.csdn.net/pistolove/article/details/42041757(重構筆記——代碼的壞味道(上))
10、https://www.cnblogs.com/bell1991/p/7797947.html(對高內聚珠插,低耦合的理解)
11惧磺、https://blog.csdn.net/jianyuerensheng/article/details/51602015(面向對象編程三大特性------封裝、繼承捻撑、多態(tài))
12磨隘、https://blog.csdn.net/zhangerqing/article/list/2(Java之美[從菜鳥到高手演變]之設計模式系列)