<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          常見(jiàn)代碼重構(gòu)技巧(非常實(shí)用)

          共 11372字,需瀏覽 23分鐘

           ·

          2021-11-14 04:45

          點(diǎn)擊下方“IT牧場(chǎng)”,選擇“設(shè)為星標(biāo)”

          來(lái)自:VectorJin

          鏈接:juejin.cn/post/6954378167947624484

          關(guān)于重構(gòu)

          為什么要重構(gòu)

          1_代碼重構(gòu)漫畫(huà).jpeg

          項(xiàng)目在不斷演進(jìn)過(guò)程中,代碼不停地在堆砌。如果沒(méi)有人為代碼的質(zhì)量負(fù)責(zé),代碼總是會(huì)往越來(lái)越混亂的方向演進(jìn)。當(dāng)混亂到一定程度之后,量變引起質(zhì)變,項(xiàng)目的維護(hù)成本已經(jīng)高過(guò)重新開(kāi)發(fā)一套新代碼的成本,想要再去重構(gòu),已經(jīng)沒(méi)有人能做到了。

          造成這樣的原因往往有以下幾點(diǎn):

          1. 編碼之前缺乏有效的設(shè)計(jì)

          2. 成本上的考慮,在原功能堆砌式編程

          3. 缺乏有效代碼質(zhì)量監(jiān)督機(jī)制

          對(duì)于此類(lèi)問(wèn)題,業(yè)界已有有很好的解決思路:通過(guò)持續(xù)不斷的重構(gòu)將代碼中的“壞味道”清除掉。

          什么是重構(gòu)

          重構(gòu)一書(shū)的作者M(jìn)artin Fowler對(duì)重構(gòu)的定義:

          重構(gòu)(名詞):對(duì)軟件內(nèi)部結(jié)構(gòu)的一種調(diào)整,目的是在不改變軟件可觀察行為的前提下,提高其可理解性,降低其修改成本。
          重構(gòu)(動(dòng)詞):使用一系列重構(gòu)手法,在不改變軟件可觀察行為的前提下,調(diào)整其結(jié)構(gòu)。

          根據(jù)重構(gòu)的規(guī)模可以大致分為大型重構(gòu)和小型重構(gòu):

          大型重構(gòu):對(duì)頂層代碼設(shè)計(jì)的重構(gòu),包括:系統(tǒng)、模塊、代碼結(jié)構(gòu)、類(lèi)與類(lèi)之間的關(guān)系等的重構(gòu),重構(gòu)的手段有:分層、模塊化、解耦、抽象可復(fù)用組件等等。這類(lèi)重構(gòu)的工具就是我們學(xué)習(xí)過(guò)的那些設(shè)計(jì)思想、原則和模式。這類(lèi)重構(gòu)涉及的代碼改動(dòng)會(huì)比較多,影響面會(huì)比較大,所以難度也較大,耗時(shí)會(huì)比較長(zhǎng),引入bug的風(fēng)險(xiǎn)也會(huì)相對(duì)比較大。

          小型重構(gòu):對(duì)代碼細(xì)節(jié)的重構(gòu),主要是針對(duì)類(lèi)、函數(shù)、變量等代碼級(jí)別的重構(gòu),比如規(guī)范命名和注釋、消除超大類(lèi)或函數(shù)、提取重復(fù)代碼等等。小型重構(gòu)更多的是使用統(tǒng)一的編碼規(guī)范。這類(lèi)重構(gòu)要修改的地方比較集中,比較簡(jiǎn)單,可操作性較強(qiáng),耗時(shí)會(huì)比較短,引入bug的風(fēng)險(xiǎn)相對(duì)來(lái)說(shuō)也會(huì)比較小。什么時(shí)候重構(gòu) 新功能開(kāi)發(fā)、修bug或者代碼review中出現(xiàn)“代碼壞味道”,我們就應(yīng)該及時(shí)進(jìn)行重構(gòu)。持續(xù)在日常開(kāi)發(fā)中進(jìn)行小重構(gòu),能夠降低重構(gòu)和測(cè)試的成本。

          代碼的壞味道

          2_代碼常見(jiàn)問(wèn)題.png

          代碼重復(fù)

          • 實(shí)現(xiàn)邏輯相同、執(zhí)行流程相同

          方法過(guò)長(zhǎng)

          • 方法中的語(yǔ)句不在同一個(gè)抽象層級(jí)

          • 邏輯難以理解,需要大量的注釋

          • 面向過(guò)程編程而非面向?qū)ο?/p>

          過(guò)大的類(lèi)

          • 類(lèi)做了太多的事情

          • 包含過(guò)多的實(shí)例變量和方法

          • 類(lèi)的命名不足以描述所做的事情

          邏輯分散

          • 發(fā)散式變化:某個(gè)類(lèi)經(jīng)常因?yàn)椴煌脑蛟诓煌姆较蛏习l(fā)生變化

          • 散彈式修改:發(fā)生某種變化時(shí),需要在多個(gè)類(lèi)中做修改

          嚴(yán)重的情結(jié)依戀

          • 某個(gè)類(lèi)的方法過(guò)多的使用其他類(lèi)的成員

          數(shù)據(jù)泥團(tuán)/基本類(lèi)型偏執(zhí)

          • 兩個(gè)類(lèi)、方法簽名中包含相同的字段或參數(shù)

          • 應(yīng)該使用類(lèi)但使用基本類(lèi)型,比如表示數(shù)值與幣種的Money類(lèi)、起始值與結(jié)束值的Range類(lèi)

          不合理的繼承體系

          • 繼承打破了封裝性,子類(lèi)依賴其父類(lèi)中特定功能的實(shí)現(xiàn)細(xì)節(jié)

          • 子類(lèi)必須跟著其父類(lèi)的更新而演變,除非父類(lèi)是專(zhuān)門(mén)為了擴(kuò)展而設(shè)計(jì),并且有很好的文檔說(shuō)明

          過(guò)多的條件判斷

          過(guò)長(zhǎng)的參數(shù)列

          臨時(shí)變量過(guò)多

          令人迷惑的暫時(shí)字段

          • 某個(gè)實(shí)例變量?jī)H為某種特定情況而設(shè)置

          • 將實(shí)例變量與相應(yīng)的方法提取到新的類(lèi)中

          純數(shù)據(jù)類(lèi)

          • 僅包含字段和訪問(wèn)(讀寫(xiě))這些字段的方法

          • 此類(lèi)被稱(chēng)為數(shù)據(jù)容器,應(yīng)保持最小可變性

          不恰當(dāng)?shù)拿?/strong>

          • 命名無(wú)法準(zhǔn)確描述做的事情

          • 命名不符合約定俗稱(chēng)的慣例

          過(guò)多的注釋

          壞代碼的問(wèn)題

          • 難以復(fù)用

          • 系統(tǒng)關(guān)聯(lián)性過(guò)多,導(dǎo)致很難分離可重用部分

          • 難于變化

          • 一處變化導(dǎo)致其他很多部分的修改,不利于系統(tǒng)穩(wěn)定

          • 難于理解

          • 命名雜亂,結(jié)構(gòu)混亂,難于閱讀和理解

          • 難以測(cè)試

          • 分支、依賴較多,難以覆蓋全面

          什么是好代碼

          3_代碼質(zhì)量如何衡量.jpg

          代碼質(zhì)量的評(píng)價(jià)有很強(qiáng)的主觀性,描述代碼質(zhì)量的詞匯也有很多,比如可讀性、可維護(hù)性、靈活、優(yōu)雅、簡(jiǎn)潔。這些詞匯是從不同的維度去評(píng)價(jià)代碼質(zhì)量的。其中,可維護(hù)性、可讀性、可擴(kuò)展性又是提到最多的、最重要的三個(gè)評(píng)價(jià)標(biāo)準(zhǔn)。

          要寫(xiě)出高質(zhì)量代碼,我們就需要掌握一些更加細(xì)化、更加能落地的編程方法論,這就包含面向?qū)ο笤O(shè)計(jì)思想、設(shè)計(jì)原則、設(shè)計(jì)模式、編碼規(guī)范、重構(gòu)技巧等。

          如何重構(gòu)

          SOLID原則

          4_SOLID原則.png

          單一職責(zé)原則

          一個(gè)類(lèi)只負(fù)責(zé)完成一個(gè)職責(zé)或者功能,不要存在多于一種導(dǎo)致類(lèi)變更的原因。

          單一職責(zé)原則通過(guò)避免設(shè)計(jì)大而全的類(lèi),避免將不相關(guān)的功能耦合在一起,來(lái)提高類(lèi)的內(nèi)聚性。同時(shí),類(lèi)職責(zé)單一,類(lèi)依賴的和被依賴的其他類(lèi)也會(huì)變少,減少了代碼的耦合性,以此來(lái)實(shí)現(xiàn)代碼的高內(nèi)聚、松耦合。但是,如果拆分得過(guò)細(xì),實(shí)際上會(huì)適得其反,反倒會(huì)降低內(nèi)聚性,也會(huì)影響代碼的可維護(hù)性。

          開(kāi)放-關(guān)閉原則

          添加一個(gè)新的功能,應(yīng)該是通過(guò)在已有代碼基礎(chǔ)上擴(kuò)展代碼(新增模塊、類(lèi)、方法、屬性等),而非修改已有代碼(修改模塊、類(lèi)、方法、屬性等)的方式來(lái)完成。

          開(kāi)閉原則并不是說(shuō)完全杜絕修改,而是以最小的修改代碼的代價(jià)來(lái)完成新功能的開(kāi)發(fā)。

          很多設(shè)計(jì)原則、設(shè)計(jì)思想、設(shè)計(jì)模式,都是以提高代碼的擴(kuò)展性為最終目的的。特別是 23 種經(jīng)典設(shè)計(jì)模式,大部分都是為了解決代碼的擴(kuò)展性問(wèn)題而總結(jié)出來(lái)的,都是以開(kāi)閉原則為指導(dǎo)原則的。最常用來(lái)提高代碼擴(kuò)展性的方法有:多態(tài)、依賴注入、基于接口而非實(shí)現(xiàn)編程,以及大部分的設(shè)計(jì)模式(比如,裝飾、策略、模板、職責(zé)鏈、狀態(tài))。

          里氏替換原則

          子類(lèi)對(duì)象(object of subtype/derived class)能夠替換程序(program)中父類(lèi)對(duì)象(object of base/parent class)出現(xiàn)的任何地方,并且保證原來(lái)程序的邏輯行為(behavior)不變及正確性不被破壞。

          子類(lèi)可以擴(kuò)展父類(lèi)的功能,但不能改變父類(lèi)原有的功能

          父類(lèi)中凡是已經(jīng)實(shí)現(xiàn)好的方法(相對(duì)于抽象方法而言),實(shí)際上是在設(shè)定一系列的規(guī)范和契約,雖然它不強(qiáng)制要求所有的子類(lèi)必須遵從這些契約,但是如果子類(lèi)對(duì)這些非抽象方法任意修改,就會(huì)對(duì)整個(gè)繼承體系造成破壞。

          接口隔離原則

          調(diào)用方不應(yīng)該依賴它不需要的接口;一個(gè)類(lèi)對(duì)另一個(gè)類(lèi)的依賴應(yīng)該建立在最小的接口上。接口隔離原則提供了一種判斷接口的職責(zé)是否單一的標(biāo)準(zhǔn):通過(guò)調(diào)用者如何使用接口來(lái)間接地判定。如果調(diào)用者只使用部分接口或接口的部分功能,那接口的設(shè)計(jì)就不夠職責(zé)單一。

          依賴反轉(zhuǎn)原則

          高層模塊不應(yīng)該依賴低層模塊,二者都應(yīng)該依賴其抽象;抽象不應(yīng)該依賴細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴抽象。

          迪米特法則

          一個(gè)對(duì)象應(yīng)該對(duì)其他對(duì)象保持最少的了解

          合成復(fù)用原則

          盡量使用合成/聚合的方式,而不是使用繼承。

          單一職責(zé)原則告訴我們實(shí)現(xiàn)類(lèi)要職責(zé)單一;里氏替換原則告訴我們不要破壞繼承體系;依賴倒置原則告訴我們要面向接口編程;接口隔離原則告訴我們?cè)谠O(shè)計(jì)接口的時(shí)候要精簡(jiǎn)單一;迪米特法則告訴我們要降低耦合。而開(kāi)閉原則是總綱,告訴我們要對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉。

          設(shè)計(jì)模式

          設(shè)計(jì)模式:軟件開(kāi)發(fā)人員在軟件開(kāi)發(fā)過(guò)程中面臨的一般問(wèn)題的解決方案。這些解決方案是眾多軟件開(kāi)發(fā)人員經(jīng)過(guò)相當(dāng)長(zhǎng)的一段時(shí)間的試驗(yàn)和錯(cuò)誤總結(jié)出來(lái)的。每種模式都描述了一個(gè)在我們周?chē)粩嘀貜?fù)發(fā)生的問(wèn)題,以及該問(wèn)題的核心解決方案。

          • 創(chuàng)建型:主要解決對(duì)象的創(chuàng)建問(wèn)題,封裝復(fù)雜的創(chuàng)建過(guò)程,解耦對(duì)象的創(chuàng)建代碼和使用代碼

          • 結(jié)構(gòu)型:主要通過(guò)類(lèi)或?qū)ο蟮牟煌M合,解耦不同功能的耦合

          • 行為型:主要解決的是類(lèi)或?qū)ο笾g的交互行為的耦合

          代碼分層

          image.png

          模塊結(jié)構(gòu)說(shuō)明

          • server_main:配置層,負(fù)責(zé)整個(gè)項(xiàng)目的module管理,maven配置管理、資源管理等;

          • server_application:應(yīng)用接入層,承接外部流量入口,例如:RPC接口實(shí)現(xiàn)、消息處理、定時(shí)任務(wù)等;不要在此包含業(yè)務(wù)邏輯;

          • server_biz:核心業(yè)務(wù)層,用例服務(wù)、領(lǐng)域?qū)嶓w、領(lǐng)域事件等

          • server_irepository:資源接口層,負(fù)責(zé)資源接口的暴露

          • server_repository:資源層,負(fù)責(zé)資源的proxy訪問(wèn),統(tǒng)一外部資源訪問(wèn),隔離變化。注意:這里強(qiáng)調(diào)的是弱業(yè)務(wù)性,強(qiáng)數(shù)據(jù)性;

          • server_common:公共層,vo、工具等

          代碼開(kāi)發(fā)要遵守各層的規(guī)范,并注意層級(jí)之間的依賴關(guān)系。

          命名規(guī)范

          一個(gè)好的命名應(yīng)該要滿足以下兩個(gè)約束:

          • 準(zhǔn)確描述所做得事情

          • 格式符合通用的慣例

          如果你覺(jué)得一個(gè)類(lèi)或方法難以命名的時(shí)候,可能是其承載的功能太多了,需要進(jìn)一步拆分。

          搜索Linux中文社區(qū)公眾號(hào)回復(fù)“命令行”,送你一份驚喜禮包。

          約定俗稱(chēng)的慣例

          類(lèi)命名

          類(lèi)名使用大駝峰命名形式,類(lèi)命通常使用名詞或名詞短語(yǔ)。接口名除了用名詞和名詞短語(yǔ)以外,還可以使用形容詞或形容詞短語(yǔ),如 Cloneable,Callable 等,表示實(shí)現(xiàn)該接口的類(lèi)有某種功能或能力。

          方法命名

          方法命名采用小駝峰的形式,首字小寫(xiě),往后的每個(gè)單詞首字母都要大寫(xiě)。和類(lèi)名不同的是,方法命名一般為動(dòng)詞或動(dòng)詞短語(yǔ),與參數(shù)或參數(shù)名共同組成動(dòng)賓短語(yǔ),即動(dòng)詞 + 名詞。一個(gè)好的函數(shù)名一般能通過(guò)名字直接獲知該函數(shù)實(shí)現(xiàn)什么樣的功能。

          重構(gòu)技巧

          提煉方法

          多個(gè)方法代碼重復(fù)、方法中代碼過(guò)長(zhǎng)或者方法中的語(yǔ)句不在一個(gè)抽象層級(jí)。
          方法是代碼復(fù)用的最小粒度,方法過(guò)長(zhǎng)不利于復(fù)用,可讀性低,提煉方法往往是重構(gòu)工作的第一步。

          意圖導(dǎo)向編程:把處理某件事的流程和具體做事的實(shí)現(xiàn)方式分開(kāi)。

          • 把一個(gè)問(wèn)題分解為一系列功能性步驟,并假定這些功能步驟已經(jīng)實(shí)現(xiàn)

          • 我們只需把把各個(gè)函數(shù)組織在一起即可解決這一問(wèn)題

          • 在組織好整個(gè)功能后,我們?cè)诜謩e實(shí)現(xiàn)各個(gè)方法函數(shù)

          /**?
          ??* 1、交易信息開(kāi)始于一串標(biāo)準(zhǔn)ASCII字符串。?
          ??* 2、這個(gè)信息字符串必須轉(zhuǎn)換成一個(gè)字符串的數(shù)組,數(shù)組存放的此次交易的領(lǐng)域語(yǔ)言中所包含的詞匯元素(token)。?
          ??* 3、每一個(gè)詞匯必須標(biāo)準(zhǔn)化。?
          ??* 4、包含超過(guò)150個(gè)詞匯元素的交易,應(yīng)該采用不同于小型交易的方式(不同的算法)來(lái)提交,以提高效率。?
          ??* 5、如果提交成功,API返回”true”;失敗,則返回”false”。?
          ??*/

          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;????
          ??}
          }

          以函數(shù)對(duì)象取代函數(shù)

          將函數(shù)放進(jìn)一個(gè)單獨(dú)對(duì)象中,如此一來(lái)局部變量就變成了對(duì)象內(nèi)的字段。然后你可以在同一個(gè)對(duì)象中將這個(gè)大型函數(shù)分解為多個(gè)小型函數(shù)。

          引入?yún)?shù)對(duì)象

          方法參數(shù)比較多時(shí),將參數(shù)封裝為參數(shù)對(duì)象

          移除對(duì)參數(shù)的賦值

          public?int?discount(int?inputVal,?int?quantity,?int?yearToDate)?{
          ??if?(inputVal?>?50)?inputVal?-=?2;
          ??if?(quantity?>?100)?inputVal?-=?1;
          ??if?(yearToDate?>?10000)?inputVal?-=?4;
          ??return?inputVal;
          }

          public?int?discount(int?inputVal,?int?quantity,?int?yearToDate)?{?
          ??int?result?=?inputVal;
          ??if?(inputVal?>?50)?result?-=?2;?
          ??if?(quantity?>?100)?result?-=?1;?
          ??if?(yearToDate?>?10000)?result?-=?4;?
          ??return?result;?
          }

          將查詢與修改分離

          任何有返回值的方法,都不應(yīng)該有副作用

          • 不要在convert中調(diào)用寫(xiě)操作,避免副作用

          • 常見(jiàn)的例外:將查詢結(jié)果緩存到本地

          移除不必要臨時(shí)變量

          臨時(shí)變量?jī)H使用一次或者取值邏輯成本很低的情況下

          引入解釋性變量

          將復(fù)雜表達(dá)式(或其中一部分)的結(jié)果放進(jìn)一個(gè)臨時(shí)變量,以此變量名稱(chēng)來(lái)解釋表達(dá)式用途

          if?((platform.toUpperCase().indexOf("MAC")?>?-1)?
          ????&&?(browser.toUpperCase().indexOf("IE")?>?-1)?&&?wasInitialized()?&&?resize?>?0)?{???
          ??//?do?something?
          }?
          ??
          final?boolean?isMacOs?=?platform.toUpperCase().indexOf("MAC")?>?-1;?
          final?boolean?isIEBrowser?=?browser.toUpperCase().indexOf("IE")?>?-1;?
          final?boolean?wasResized?=?resize?>?0;?
          if?(isMacOs?&&?isIEBrowser?&&?wasInitialized()?&&?wasResized)?{???
          ??//?do?something?
          }

          使用衛(wèi)語(yǔ)句替代嵌套條件判斷

          把復(fù)雜的條件表達(dá)式拆分成多個(gè)條件表達(dá)式,減少嵌套。嵌套了好幾層的if - then-else語(yǔ)句,轉(zhuǎn)換為多個(gè)if語(yǔ)句

          //未使用衛(wèi)語(yǔ)句
          public?void?getHello(int?type)?{
          ????if?(type?==?1)?{
          ????????return;
          ????}?else?{
          ????????if?(type?==?2)?{
          ????????????return;
          ????????}?else?{
          ????????????if?(type?==?3)?{
          ????????????????return;
          ????????????}?else?{
          ????????????????setHello();
          ????????????}
          ????????}
          ????}
          }?

          //使用衛(wèi)語(yǔ)句
          public?void?getHello(int?type)?{
          ????if?(type?==?1)?{
          ????????return;
          ????}
          ????if?(type?==?2)?{
          ????????return;
          ????}
          ????if?(type?==?3)?{
          ????????return;
          ????}
          ????setHello();
          }

          使用多態(tài)替代條件判斷斷

          當(dāng)存在這樣一類(lèi)條件表達(dá)式,它根據(jù)對(duì)象類(lèi)型的不同選擇不同的行為。可以將這種表達(dá)式的每個(gè)分支放進(jìn)一個(gè)子類(lèi)內(nèi)的復(fù)寫(xiě)函數(shù)中,然后將原始函數(shù)聲明為抽象函數(shù)。

          public?int?calculate(int?a,?int?b,?String?operator)?{
          ????int?result?=?Integer.MIN_VALUE;
          ?
          ????if?("add".equals(operator))?{
          ????????result?=?a?+?b;
          ????}?else?if?("multiply".equals(operator))?{
          ????????result?=?a?*?b;
          ????}?else?if?("divide".equals(operator))?{
          ????????result?=?a?/?b;
          ????}?else?if?("subtract".equals(operator))?{
          ????????result?=?a?-?b;
          ????}
          ????return?result;
          }

          當(dāng)出現(xiàn)大量類(lèi)型檢查和判斷時(shí),if else(或switch)語(yǔ)句的體積會(huì)比較臃腫,這無(wú)疑降低了代碼的可讀性。? 另外,if else(或switch)本身就是一個(gè)“變化點(diǎn)”,當(dāng)需要擴(kuò)展新的類(lèi)型時(shí),我們不得不追加if else(或switch)語(yǔ)句塊,以及相應(yīng)的邏輯,這無(wú)疑降低了程序的可擴(kuò)展性,也違反了面向?qū)ο蟮拈_(kāi)閉原則。

          基于這種場(chǎng)景,我們可以考慮使用“多態(tài)”來(lái)代替冗長(zhǎng)的條件判斷,將if else(或switch)中的“變化點(diǎn)”封裝到子類(lèi)中。這樣,就不需要使用if else(或switch)語(yǔ)句了,取而代之的是子類(lèi)多態(tài)的實(shí)例,從而使得提高代碼的可讀性和可擴(kuò)展性。很多設(shè)計(jì)模式使用都是這種套路,比如策略模式、狀態(tài)模式。

          public?interface?Operation?{?
          ??int?apply(int?a,?int?b);?
          }

          public?class?Addition?implements?Operation?{?
          ??@Override?
          ??public?int?apply(int?a,?int?b)?{?
          ????return?a?+?b;?
          ??}?
          }

          public?class?OperatorFactory?{
          ????private?final?static?Map?operationMap?=?new?HashMap<>();
          ????static?{
          ????????operationMap.put("add",?new?Addition());
          ????????operationMap.put("divide",?new?Division());
          ????????//?more?operators
          ????}
          ?
          ????public?static?Operation?getOperation(String?operator)?{
          ????????return?operationMap.get(operator);
          ????}
          }

          public?int?calculate(int?a,?int?b,?String?operator)?{
          ????if?(OperatorFactory?.getOperation?==?null)?{
          ???????throw?new?IllegalArgumentException("Invalid?Operator");
          ????}
          ????return?OperatorFactory?.getOperation(operator).apply(a,?b);
          }

          使用異常替代返回錯(cuò)誤碼

          非正常業(yè)務(wù)狀態(tài)的處理,使用拋出異常的方式代替返回錯(cuò)誤碼

          • 不要使用異常處理用于正常的業(yè)務(wù)流程控制
            • 異常處理的性能成本非常高
          • 盡量使用標(biāo)準(zhǔn)異常
          • 避免在finally語(yǔ)句塊中拋出異常
            • 如果同時(shí)拋出兩個(gè)異常,則第一個(gè)異常的調(diào)用棧會(huì)丟失
            • finally塊中應(yīng)只做關(guān)閉資源這類(lèi)的事情
          //使用錯(cuò)誤碼
          public?boolean?withdraw(int?amount)?{
          ????if?(balance?????????return?false;
          ????}?else?{
          ????????balance?-=?amount;
          ????????return?true;
          ????}
          }

          //使用異常
          public?void?withdraw(int?amount)?{
          ????if?(amount?>?balance)?{
          ????????throw?new?IllegalArgumentException("amount?too?large");????
          ????}
          ????balance?-=?amount;
          }

          引入斷言

          某一段代碼需要對(duì)程序狀態(tài)做出某種假設(shè),以斷言明確表現(xiàn)這種假設(shè)。

          • 不要濫用斷言,不要使用它來(lái)檢查“應(yīng)該為真”的條件,只使用它來(lái)檢查“一定必須為真”的條件

          • 如果斷言所指示的約束條件不能滿足,代碼是否仍能正常運(yùn)行?如果可以就去掉斷言

          引入Null對(duì)象或特殊對(duì)象

          當(dāng)使用一個(gè)方法返回的對(duì)象時(shí),而這個(gè)對(duì)象可能為空,這個(gè)時(shí)候需要對(duì)這個(gè)對(duì)象進(jìn)行操作前,需要進(jìn)行判空,否則就會(huì)報(bào)空指針。當(dāng)這種判斷頻繁的出現(xiàn)在各處代碼之中,就會(huì)影響代碼的美觀程度和可讀性,甚至增加Bug的幾率。

          空引用的問(wèn)題在Java中無(wú)法避免,但可以通過(guò)代碼編程技巧(引入空對(duì)象)來(lái)改善這一問(wèn)題。

          //空對(duì)象的例子
          public?class?OperatorFactory?{?
          ??static?Map?operationMap?=?new?HashMap<>();?
          ??static?{?
          ????operationMap.put("add",?new?Addition());?
          ????operationMap.put("divide",?new?Division());?
          ????//?more?operators?
          ??}?
          ??public?static?Optional?getOperation(String?operator)?{?
          ????return?Optional.ofNullable(operationMap.get(operator));?
          ??}?
          }?
          public?int?calculate(int?a,?int?b,?String?operator)?{?
          ??Operation?targetOperation?=?OperatorFactory.getOperation(operator)?
          ?????.orElseThrow(()?->?new?IllegalArgumentException("Invalid?Operator"));?
          ??return?targetOperation.apply(a,?b);?
          }

          //特殊對(duì)象的例子
          public?class?InvalidOp?implements?Operation?{?
          ??@Override?
          ??public?int?apply(int?a,?int?b)??{?
          ????throw?new?IllegalArgumentException("Invalid?Operator");
          ??}?
          }

          提煉類(lèi)

          根據(jù)單一職責(zé)原則,一個(gè)類(lèi)應(yīng)該有明確的責(zé)任邊界。但在實(shí)際工作中,類(lèi)會(huì)不斷的擴(kuò)展。當(dāng)給某個(gè)類(lèi)添加一項(xiàng)新責(zé)任時(shí),你會(huì)覺(jué)得不值得分離出一個(gè)單獨(dú)的類(lèi)。于是,隨著責(zé)任不斷增加,這個(gè)類(lèi)包含了大量的數(shù)據(jù)和函數(shù),邏輯復(fù)雜不易理解。

          此時(shí)你需要考慮將哪些部分分離到一個(gè)單獨(dú)的類(lèi)中,可以依據(jù)高內(nèi)聚低耦合的原則。如果某些數(shù)據(jù)和方法總是一起出現(xiàn),或者某些數(shù)據(jù)經(jīng)常同時(shí)變化,這就表明它們應(yīng)該放到一個(gè)類(lèi)中。另一種信號(hào)是類(lèi)的子類(lèi)化方式:如果你發(fā)現(xiàn)子類(lèi)化只影響類(lèi)的部分特性,或者類(lèi)的特性需要以不同方式來(lái)子類(lèi)化,這就意味著你需要分解原來(lái)的類(lèi)。

          //原始類(lèi)
          public?class?Person?{
          ????private?String?name;
          ????private?String?officeAreaCode;
          ????private?String?officeNumber;

          ????public?String?getName()?{
          ????????return?name;
          ????}

          ????public?String?getTelephoneNumber()?{
          ????????return?("("?+?officeAreaCode?+?")"?+?officeNumber);
          ????}

          ????public?String?getOfficeAreaCode()?{
          ????????return?officeAreaCode;
          ????}

          ????public?void?setOfficeAreaCode(String?arg)?{
          ????????officeAreaCode?=?arg;
          ????}

          ????public?String?getOfficeNumber()?{
          ????????return?officeNumber;
          ????}

          ????public?void?setOfficeNumber(String?arg)?{
          ????????officeNumber?=?arg;
          ????}
          }

          //新提煉的類(lèi)(以對(duì)象替換數(shù)據(jù)值)
          public?class?TelephoneNumber?{
          ????private?String?areaCode;
          ????private?String?number;

          ????public?String?getTelephnoeNumber()?{
          ????????return?("("?+?getAreaCode()?+?")"?+?number);
          ????}

          ????String?getAreaCode()?{
          ????????return?areaCode;
          ????}

          ????void?setAreaCode(String?arg)?{
          ????????areaCode?=?arg;
          ????}

          ????String?getNumber()?{
          ????????return?number;
          ????}

          ????void?setNumber(String?arg)?{
          ????????number?=?arg;
          ????}
          }

          組合優(yōu)先于繼承

          繼承使實(shí)現(xiàn)代碼重用的有力手段,但這并非總是完成這項(xiàng)工作的最佳工具,使用不當(dāng)會(huì)導(dǎo)致軟件變得很脆弱。與方法調(diào)用不同的是,繼承打破了封裝性。子類(lèi)依賴于其父類(lèi)中特定功能的實(shí)現(xiàn)細(xì)節(jié),如果父類(lèi)的實(shí)現(xiàn)隨著發(fā)行版本的不同而變化,子類(lèi)可能會(huì)遭到破壞,即使他的代碼完全沒(méi)有改變。

          舉例說(shuō)明,假設(shè)有一個(gè)程序使用HashSet,為了調(diào)優(yōu)該程序的性能,需要統(tǒng)計(jì)HashSet自從它創(chuàng)建以來(lái)添加了多少個(gè)元素。為了提供該功能,我們編寫(xiě)一個(gè)HashSet的變體。

          //?Inappropriate?use?of?inheritance!
          public?class?InstrumentedHashSet<E>?extends?HashSet<E>?{
          ????//?The?number?of?attempted?element?insertions
          ????private?int?addCount?=?0;

          ????public?InstrumentedHashSet()?{?}

          ????public?InstrumentedHashSet(int?initCap,?float?loadFactor)?{
          ????????super(initCap,?loadFactor);
          ????}

          ????@Override
          ????public?boolean?add(E?e)?{
          ????????addCount++;
          ????????return?super.add(e);
          ????}

          ????@Override
          ????public?boolean?addAll(Collection?c)?{
          ????????addCount?+=?c.size();
          ????????return?super.addAll(c);
          ????}

          ????public?int?getAddCount()?{
          ????????return?addCount;
          ????}
          }

          通過(guò)在新的類(lèi)中增加一個(gè)私有域,它引用現(xiàn)有類(lèi)的一個(gè)實(shí)例,這種設(shè)計(jì)被稱(chēng)為組合,因?yàn)楝F(xiàn)有的類(lèi)變成了新類(lèi)的一個(gè)組件。這樣得到的類(lèi)將會(huì)非常穩(wěn)固,它不依賴現(xiàn)有類(lèi)的實(shí)現(xiàn)細(xì)節(jié)。即使現(xiàn)有的類(lèi)添加了新的方法,也不會(huì)影響新的類(lèi)。許多設(shè)計(jì)模式使用就是這種套路,比如代理模式、裝飾者模式

          //?Reusable?forwarding?class
          public?class?ForwardingSet<E>?implements?Set<E>?{
          ????private?final?Set?s;
          ????public?ForwardingSet(Set?s)?{?this.s?=?s;?}
          ??
          ????@Override
          ????public?int?size()?{?return?s.size();?}
          ????@Override
          ????public?boolean?isEmpty()?{?return?s.isEmpty();?}
          ????@Override
          ????public?boolean?contains(Object?o)?{?return?s.contains(o);?}
          ????@Override
          ????public?Iterator?iterator()?{?return?s.iterator();?}
          ????@Override
          ????public?Object[]?toArray()?{?return?s.toArray();?}
          ????@Override
          ????public??T[]?toArray(T[]?a)?{?return?s.toArray(a);?}
          ????@Override
          ????public?boolean?add(E?e)?{?return?s.add(e);?}
          ????@Override
          ????public?boolean?remove(Object?o)?{?return?s.remove(o);?}
          ????@Override
          ????public?boolean?containsAll(Collection?c)?{?return?s.containsAll(c);?}
          ????@Override
          ????public?boolean?addAll(Collection?c)?{?return?s.addAll(c);?}
          ????@Override
          ????public?boolean?retainAll(Collection?c)?{?return?s.retainAll(c);?}
          ????@Override
          ????public?boolean?removeAll(Collection?c)?{?return?s.removeAll(c);?}
          ????@Override
          ????public?void?clear()?{?s.clear();?}
          }

          //?Wrappter?class?-?uses?composition?in?place?of?inheritance
          public?class?InstrumentedHashSet<E>?extends?ForwardingSet<E>?{
          ????private?int?addCount?=?0;

          ????public?InstrumentedHashSet1(Set?s)?{
          ????????super(s);
          ????}

          ????@Override
          ????public?boolean?add(E?e)?{
          ????????addCount++;
          ????????return?super.add(e);
          ????}

          ????@Override
          ????public?boolean?addAll(Collection?c)?{
          ????????addCount?+=?c.size();
          ????????return?super.addAll(c);
          ????}

          ????public?int?getAddCount()?{
          ????????return?addCount;
          ????}
          }

          繼承與組合如何取舍

          • 只有當(dāng)子類(lèi)真正是父類(lèi)的子類(lèi)型時(shí),才適合繼承。對(duì)于兩個(gè)類(lèi)A和B,只有兩者之間確實(shí)存在“is-a”關(guān)系的時(shí)候,類(lèi)B才應(yīng)該繼承A;

          • 在包的內(nèi)部使用繼承是非常安全的,子類(lèi)和父類(lèi)的實(shí)現(xiàn)都處在同一個(gè)程序員的控制之下;

          • 對(duì)于專(zhuān)門(mén)為了繼承而設(shè)計(jì)并且具有很好的文檔說(shuō)明的類(lèi)來(lái)說(shuō),使用繼承也是非常安全的;

          • 其他情況就應(yīng)該優(yōu)先考慮組合的方式來(lái)實(shí)現(xiàn)

          接口優(yōu)于抽象類(lèi)

          Java提供了兩種機(jī)制,可以用來(lái)定義允許多個(gè)實(shí)現(xiàn)的類(lèi)型:接口和抽象類(lèi)。自從Java8為接口增加缺省方法(default method),這兩種機(jī)制都允許為實(shí)例方法提供實(shí)現(xiàn)。主要區(qū)別在于,為了實(shí)現(xiàn)由抽象類(lèi)定義的類(lèi)型,類(lèi)必須稱(chēng)為抽象類(lèi)的一個(gè)子類(lèi)。因?yàn)镴ava只允許單繼承,所以用抽象類(lèi)作為類(lèi)型定義受到了限制。

          搜索頂級(jí)架構(gòu)師公眾號(hào)回復(fù)“架構(gòu)整潔”,送你一份驚喜禮包。

          接口相比于抽象類(lèi)的優(yōu)勢(shì):

          • 現(xiàn)有的類(lèi)可以很容易被更新,以實(shí)現(xiàn)新的接口。

          • 接口是定義混合類(lèi)型(比如Comparable)的理想選擇。

          • 接口允許構(gòu)造非層次結(jié)構(gòu)的類(lèi)型框架。

          接口雖然提供了缺省方法,但接口仍有有以下局限性:

          • 接口的變量修飾符只能是public static final的

          • 接口的方法修飾符只能是public的

          • 接口不存在構(gòu)造函數(shù),也不存在this

          • 可以給現(xiàn)有接口增加缺省方法,但不能確保這些方法在之前存在的實(shí)現(xiàn)中都能良好運(yùn)行。

          • 因?yàn)檫@些默認(rèn)方法是被注入到現(xiàn)有實(shí)現(xiàn)中的,它們的實(shí)現(xiàn)者并不知道,也沒(méi)有許可

          接口缺省方法的設(shè)計(jì)目的和優(yōu)勢(shì)在于:

          為了接口的演化

          • Java 8 之前我們知道,一個(gè)接口的所有方法其子類(lèi)必須實(shí)現(xiàn)(當(dāng)然,這個(gè)子類(lèi)不是一個(gè)抽象類(lèi)),但是 java 8 之后接口的默認(rèn)方法可以選擇不實(shí)現(xiàn),如上的操作是可以通過(guò)編譯期編譯的。這樣就避免了由 Java 7 升級(jí)到 Java 8 時(shí)項(xiàng)目編譯報(bào)錯(cuò)了。Java8在核心集合接口中增加了許多新的缺省方法,主要是為了便于使用lambda。

          可以減少第三方工具類(lèi)的創(chuàng)建

          • 例如在 List 等集合接口中都有一些默認(rèn)方法,List 接口中默認(rèn)提供 replaceAll(UnaryOperator)、sort(Comparator)、、spliterator()等默認(rèn)方法,這些方法在接口內(nèi)部創(chuàng)建,避免了為了這些方法而專(zhuān)門(mén)去創(chuàng)建相應(yīng)的工具類(lèi)。

          可以避免創(chuàng)建基類(lèi)

          • 在 Java 8 之前我們可能需要?jiǎng)?chuàng)建一個(gè)基類(lèi)來(lái)實(shí)現(xiàn)代碼復(fù)用,而默認(rèn)方法的出現(xiàn),可以不必要去創(chuàng)建基類(lèi)。

          由于接口的局限性和設(shè)計(jì)目的的不同,接口并不能完全替換抽象類(lèi)。但是通過(guò)對(duì)接口提供一個(gè)抽象的骨架實(shí)現(xiàn)類(lèi),可以把接口和抽象類(lèi)的優(yōu)點(diǎn)結(jié)合起來(lái)。?接口負(fù)責(zé)定義類(lèi)型,或許還提供一些缺省方法,而骨架實(shí)現(xiàn)類(lèi)則負(fù)責(zé)實(shí)現(xiàn)除基本類(lèi)型接口方法之外,剩下的非基本類(lèi)型接口方法。擴(kuò)展骨架實(shí)現(xiàn)占了實(shí)現(xiàn)接口之外的大部分工作。這就是模板方法(Template Method)設(shè)計(jì)模式。

          Image [5].png

          接口Protocol:定義了RPC協(xié)議層兩個(gè)主要的方法,export暴露服務(wù)和refer引用服務(wù)

          抽象類(lèi)AbstractProtocol:封裝了暴露服務(wù)之后的Exporter和引用服務(wù)之后的Invoker實(shí)例,并實(shí)現(xiàn)了服務(wù)銷(xiāo)毀的邏輯

          具體實(shí)現(xiàn)類(lèi)XxxProtocol:實(shí)現(xiàn)export暴露服務(wù)和refer引用服務(wù)具體邏輯

          優(yōu)先考慮泛型

          聲明中具有一個(gè)或者多個(gè)類(lèi)型參數(shù)(type parameter)的類(lèi)或者接口,就是泛型(generic)類(lèi)或者接口。泛型類(lèi)和接口統(tǒng)稱(chēng)為泛型(generic type)。泛型從Java 5引入,提供了編譯時(shí)類(lèi)型安全檢測(cè)機(jī)制。泛型的本質(zhì)是參數(shù)化類(lèi)型,通過(guò)一個(gè)參數(shù)來(lái)表示所操作的數(shù)據(jù)類(lèi)型,并且可以限制這個(gè)參數(shù)的類(lèi)型范圍。泛型的好處就是編譯期類(lèi)型檢測(cè),避免類(lèi)型轉(zhuǎn)換。

          //?比較三個(gè)值并返回最大值
          public?static?>?T?maximum(T?x,?T?y,?T?z)?{???
          ??T?max?=?x;?
          ??//?假設(shè)x是初始最大值???
          ??if?(?y.compareTo(?max?)?>?0?)?{??????
          ????max?=?y;?//y?更大??
          ??}???if?(?z.compareTo(?max?)?>?0?)?{?????
          ????max?=?z;?//?現(xiàn)在?z?更大??????????????
          ??}???return?max;?//?返回最大對(duì)象
          }

          public?static?void?main(?String?args[]?)?{???
          ??System.out.printf(?"%d,?%d?和?%d?中最大的數(shù)為?%d\n\n",??3,?4,?5,?maximum(?3,?4,?5?));???
          ??System.out.printf(?"%.1f,?%.1f?和?%.1f?中最大的數(shù)為?%.1f\n\n",??6.6,?8.8,?7.7,??maximum(?6.6,?8.8,?7.7?));???
          ??System.out.printf(?"%s,?%s?和?%s?中最大的數(shù)為?%s\n","pear",?"apple",?"orange",?maximum(?"pear",?"apple",?"orange"?)?);
          }

          不要使用原生態(tài)類(lèi)型

          由于為了保持Java代碼的兼容性,支持和原生態(tài)類(lèi)型轉(zhuǎn)換,并使用擦除機(jī)制實(shí)現(xiàn)的泛型。但是使用原生態(tài)類(lèi)型就會(huì)失去泛型的優(yōu)勢(shì),會(huì)受到編譯器警告。

          要盡可能地消除每一個(gè)非受檢警告

          每一條警告都表示可能在運(yùn)行時(shí)拋出ClassCastException異常。要盡最大的努力去消除這些警告。如果無(wú)法消除但是可以證明引起警告的代碼是安全的,就可以在盡可能小的范圍中,使用@SuppressWarnings("unchecked")注解來(lái)禁止警告,但是要把禁止的原因記錄下來(lái)。

          利用有限制通配符來(lái)提升API的靈活性

          參數(shù)化類(lèi)型不支持協(xié)變的,即對(duì)于任何兩個(gè)不同的類(lèi)型Type1和Type2而言,List既不是List的子類(lèi)型,也不是它的超類(lèi)。為了解決這個(gè)問(wèn)題,提高靈活性,Java提供了一種特殊的參數(shù)化類(lèi)型,稱(chēng)作有限制的通配符類(lèi)型,即List和List。使用原則是producer-extends,consumer-super(PECS)。如果即是生產(chǎn)者,又是消費(fèi)者,就沒(méi)有必要使用通配符了。

          還有一種特殊的無(wú)限制通配符List,表示某種類(lèi)型但不確定。常用作泛型的引用,不可向其添加除Null以外的任何對(duì)象。

          //List
          //?Number?可以認(rèn)為?是Number?的?"子類(lèi)"
          List?numberArray?=?new?ArrayList();?
          //?Integer?是?Number?的子類(lèi)
          List?numberArray?=?new?ArrayList();?
          //?Double?是?Number?的子類(lèi)
          List?numberArray?=?new?ArrayList();??

          //List
          //?Integer?可以認(rèn)為是?Integer?的?"父類(lèi)"
          Listsuper?Integer>?array?=?new?ArrayList();、
          //?Number?是?Integer?的?父類(lèi)
          Listsuper?Integer>?array?=?new?ArrayList();
          //?Object?是?Integer?的?父類(lèi)
          Listsuper?Integer>?array?=?new?ArrayList();

          public?static??void?copy(Listsuper?T>?dest,?List?src)?{????
          ??int?srcSize?=?src.size();????
          ??if?(srcSize?>?dest.size())????????
          ???throw?new?IndexOutOfBoundsException("Source?does?not?fit?in?dest");????
          ??if?(srcSize?instanceof?RandomAccess?&&?dest?instanceof?RandomAccess))?{????????
          ????for?(int?i=0;?i????dest.set(i,?src.get(i));????
          ??}?else?{????????
          ????ListIteratorsuper?T>?di=dest.listIterator();????????
          ????ListIterator?si=src.listIterator();????????
          ????for?(int?i=0;?i??????di.next();????????????
          ??????di.set(si.next());????????
          ????}????
          ??}
          }

          靜態(tài)成員類(lèi)優(yōu)于非靜態(tài)成員類(lèi)

          嵌套類(lèi)(nested class)是指定義在另一個(gè)類(lèi)的內(nèi)部的類(lèi)。嵌套類(lèi)存在的目的只是為了它的外部類(lèi)提供服務(wù),如果其他的環(huán)境也會(huì)用到的話,應(yīng)該成為一個(gè)頂層類(lèi)(top-level class)。?嵌套類(lèi)有四種:靜態(tài)成員類(lèi)(static member class)、非靜態(tài)成員類(lèi)(nonstatic member class)、匿名類(lèi)(anonymous class)和 局部類(lèi)(local class)。除了第一種之外,其他三種都稱(chēng)為內(nèi)部類(lèi)(inner class)。

          匿名類(lèi)(anonymous class)

          沒(méi)有名字,聲明的同時(shí)進(jìn)行實(shí)例化,只能使用一次。當(dāng)出現(xiàn)在非靜態(tài)的環(huán)境中,會(huì)持有外部類(lèi)實(shí)例的引用。通常用于創(chuàng)建函數(shù)對(duì)象和過(guò)程對(duì)象,不過(guò)現(xiàn)在會(huì)優(yōu)先考慮lambda。

          局部類(lèi)(local class)

          任何可以聲明局部變量的地方都可以聲明局部類(lèi),同時(shí)遵循同樣的作用域規(guī)則。跟匿名類(lèi)不同的是,有名字可以重復(fù)使用。不過(guò)實(shí)際很少使用局部類(lèi)。

          靜態(tài)成員類(lèi)(static member class)

          最簡(jiǎn)單的一種嵌套類(lèi),聲明在另一個(gè)類(lèi)的內(nèi)部,是這個(gè)類(lèi)的靜態(tài)成員,遵循同樣的可訪問(wèn)性規(guī)則。常見(jiàn)的用法是作為公有的輔助類(lèi),只有與它的外部類(lèi)一起使用才有意義。

          非靜態(tài)成員類(lèi)(nonstatic member class)

          盡管語(yǔ)法上,跟靜態(tài)成員類(lèi)的唯一區(qū)別就是類(lèi)的聲明不包含static,但兩者有很大的不同。非靜態(tài)成員類(lèi)的每個(gè)實(shí)例都隱含地與外部類(lèi)的實(shí)例相關(guān)聯(lián),可以訪問(wèn)外部類(lèi)的成員屬性和方法。另外必須先創(chuàng)建外部類(lèi)的實(shí)例之后才能創(chuàng)建非靜態(tài)成員類(lèi)的實(shí)例。

          總而言之,這四種嵌套類(lèi)都有自己的用途。假設(shè)這個(gè)嵌套類(lèi)屬于一個(gè)方法的內(nèi)部,如果只需要在一個(gè)地方創(chuàng)建實(shí)例,并且已經(jīng)有了一個(gè)預(yù)置的類(lèi)型可以說(shuō)明這個(gè)類(lèi)的特征,就要把它做成匿名類(lèi)。如果一個(gè)嵌套類(lèi)需要在單個(gè)方法之外仍然可見(jiàn),或者它太長(zhǎng)了,不適合放在方法內(nèi)部,就應(yīng)該使用成員類(lèi)。如果成員類(lèi)的每個(gè)實(shí)例都需要一個(gè)指向其外圍實(shí)例的引用,就要把成員類(lèi)做成非靜態(tài)的,否則就做成靜態(tài)的。

          優(yōu)先使用模板/工具類(lèi)

          通過(guò)對(duì)常見(jiàn)場(chǎng)景的代碼邏輯進(jìn)行抽象封裝,形成相應(yīng)的模板工具類(lèi),可以大大減少重復(fù)代碼,專(zhuān)注于業(yè)務(wù)邏輯,提高代碼質(zhì)量。

          分離對(duì)象的創(chuàng)建與使用

          面向?qū)ο缶幊滔鄬?duì)于面向過(guò)程,多了實(shí)例化這一步,而對(duì)象的創(chuàng)建必須要指定具體類(lèi)型。我們常見(jiàn)的做法是“哪里用到,就在哪里創(chuàng)建”,使用實(shí)例和創(chuàng)建實(shí)例的是同一段代碼。這似乎使代碼更具有可讀性,但是某些情況下造成了不必要的耦合。

          public?class?BusinessObject?{
          ?public?void?actionMethond?{
          ?????//Other?things
          ?????Service?myServiceObj?=?new?Service();
          ???????myServiceObj.doService();
          ???????//Other?things
          ????}
          }

          public?class?BusinessObject?{
          ?public?void?actionMethond?{
          ?????//Other?things
          ?????Service?myServiceObj?=?new?ServiceImpl();
          ???????myServiceObj.doService();
          ???????//Other?things
          ????}
          }

          public?class?BusinessObject?{
          ???private?Service?myServiceObj;
          ???public?BusinessObject(Service?aService)?{
          ???????myServiceObj?=?aService;
          ????}
          ?public?void?actionMethond?{
          ?????//Other?things
          ???????myServiceObj.doService();
          ???????//Other?things
          ????}
          }

          public?class?BusinessObject?{
          ???private?Service?myServiceObj;
          ???public?BusinessObject()?{
          ???????myServiceObj?=?ServiceFactory;
          ????}
          ?public?void?actionMethond?{
          ?????//Other?things
          ???????myServiceObj.doService();
          ???????//Other?things
          ????}
          }

          對(duì)象的創(chuàng)建者耦合的是對(duì)象的具體類(lèi)型,而對(duì)象的使用者耦合的是對(duì)象的接口。也就是說(shuō),創(chuàng)建者關(guān)心的是這個(gè)對(duì)象是什么,而使用者關(guān)心的是它能干什么。這兩者應(yīng)該視為獨(dú)立的考量,它們往往會(huì)因?yàn)椴煌脑蚨淖儭?/p>

          當(dāng)對(duì)象的類(lèi)型涉及多態(tài)、對(duì)象創(chuàng)建復(fù)雜(依賴較多)可以考慮將對(duì)象的創(chuàng)建過(guò)程分離出來(lái),使得使用者不用關(guān)注對(duì)象的創(chuàng)建細(xì)節(jié)。設(shè)計(jì)模式中創(chuàng)建型模式的出發(fā)點(diǎn)就是如此,實(shí)際項(xiàng)目中可以使用工廠模式、構(gòu)建器、依賴注入的方式。

          可訪問(wèn)性最小化

          區(qū)分一個(gè)組件設(shè)計(jì)得好不好,一個(gè)很重要的因素在于,它對(duì)于外部組件而言,是否隱藏了其內(nèi)部數(shù)據(jù)和實(shí)現(xiàn)細(xì)節(jié)。Java提供了訪問(wèn)控制機(jī)制來(lái)決定類(lèi)、接口和成員的可訪問(wèn)性。實(shí)體的可訪問(wèn)性由該實(shí)體聲明所在的位置,以及該實(shí)體聲明中所出現(xiàn)的訪問(wèn)修飾符(private、protected、public)共同決定的。

          對(duì)于頂層的(非嵌套的)類(lèi)和接口,只有兩種的訪問(wèn)級(jí)別:包級(jí)私有的(沒(méi)有public修飾)和公有的(public修飾)。

          對(duì)于成員(實(shí)例/域、方法、嵌套類(lèi)和嵌套接口)由四種的訪問(wèn)級(jí)別,可訪問(wèn)性如下遞增:

          • 私有的(private修飾)--只有在聲明該成員的頂層類(lèi)內(nèi)部才可以訪問(wèn)這個(gè)成員;

          • 包級(jí)私有的(默認(rèn))--聲明該成員的包內(nèi)部的任何類(lèi)都可以訪問(wèn)這個(gè)成員;

          • 受保護(hù)的(protected修飾)--聲明該成員的類(lèi)的子類(lèi)可以訪問(wèn)這個(gè)成員,并且聲明該成員的包內(nèi)部的任何類(lèi)也可以訪問(wèn)這個(gè)成員;

          • 公有的(public修飾)--在任何地方都可以訪問(wèn)該成員;

          正確地使用這些修飾符對(duì)于實(shí)現(xiàn)信息隱藏是非常關(guān)鍵的,原則就是:盡可能地使每個(gè)類(lèi)和成員不被外界訪問(wèn)(私有或包級(jí)私有)。這樣好處就是在以后的發(fā)行版本中,可以對(duì)它進(jìn)行修改、替換或者刪除,而無(wú)須擔(dān)心會(huì)影響現(xiàn)有的客戶端程序。

          • 如果類(lèi)或接口能夠做成包級(jí)私有的,它就應(yīng)該被做成包級(jí)私有的;

          • 如果一個(gè)包級(jí)私有的頂層類(lèi)或接口只是在某一個(gè)類(lèi)的內(nèi)部被用到,就應(yīng)該考慮使它成為那個(gè)類(lèi)的私有嵌套類(lèi);

          • 公有類(lèi)不應(yīng)直接暴露實(shí)例域,應(yīng)該提供相應(yīng)的方法以保留將來(lái)改變?cè)擃?lèi)的內(nèi)部表示法的靈活性;

          • 當(dāng)確定了類(lèi)的公有API之后,應(yīng)該把其他的成員都變成私有的;

          • 如果同一個(gè)包下的類(lèi)之間存在比較多的訪問(wèn)時(shí),就要考慮重新設(shè)計(jì)以減少這種耦合;

          可變性最小化

          不可變類(lèi)是指其實(shí)例不能被修改的類(lèi)。每個(gè)實(shí)例中包含的所有信息都必須在創(chuàng)建該實(shí)例時(shí)提供,并在對(duì)象的整個(gè)生命周期內(nèi)固定不變。不可變類(lèi)好處就是簡(jiǎn)單易用、線程安全、可自由共享而不容易出錯(cuò)。Java平臺(tái)類(lèi)庫(kù)中包含許多不可變的類(lèi),比如String、基本類(lèi)型包裝類(lèi)、BigDecimal等。

          為了使類(lèi)成為不可變,要遵循下面五條規(guī)則:

          • 聲明所有的域都是私有的

          • 聲明所有的域都是final的

            • 如果一個(gè)指向新創(chuàng)建實(shí)例的引用在缺乏同步機(jī)制的情況下,從一個(gè)線程被傳遞到另一個(gè)線程,就必須確保正確的行為

          • 不提供任何會(huì)修改對(duì)象狀態(tài)的方法

          • 保證類(lèi)不會(huì)被擴(kuò)展(防止子類(lèi)化,類(lèi)聲明為final)

            • 防止粗心或者惡意的子類(lèi)假裝對(duì)象的狀態(tài)已經(jīng)改變,從而破壞該類(lèi)的不可變行為

          • 確保對(duì)任何可變組件的互斥訪問(wèn)

            • 如果類(lèi)具有指向可變對(duì)象的域,則必須確保該類(lèi)的客戶端無(wú)法獲得指向這些對(duì)象的引用。并且,永遠(yuǎn)不要用客戶端提供的對(duì)象引用來(lái)初始化這樣的域,也不要從任何訪問(wèn)方法中返回該對(duì)象引用。在構(gòu)造器、訪問(wèn)方法和readObject 方法中使用保護(hù)性拷貝技術(shù)

          可變性最小化的一些建議:

          • 除非有很好的理由要讓類(lèi)成為可變的類(lèi),否則它就應(yīng)該是不可變的;

          • 如果類(lèi)不能被做成不可變的,仍然應(yīng)該盡可能地限制它的可變性;

          • 除非有令人信服的理由要使域變成非final的,否則要使每個(gè)域都是private final的;

          • 構(gòu)造器應(yīng)該創(chuàng)建完全初始化的對(duì)象,并建立起所有的約束關(guān)系;

          質(zhì)量如何保證

          測(cè)試驅(qū)動(dòng)開(kāi)發(fā)

          測(cè)試驅(qū)動(dòng)開(kāi)發(fā)(TDD)要求以測(cè)試作為開(kāi)發(fā)過(guò)程的中心,要求在編寫(xiě)任何代碼之前,首先編寫(xiě)用于產(chǎn)碼行為的測(cè)試,而編寫(xiě)的代碼又要以使測(cè)試通過(guò)為目標(biāo)。TDD要求測(cè)試可以完全自動(dòng)化地運(yùn)行,并在對(duì)代碼重構(gòu)前后必須運(yùn)行測(cè)試。

          TDD的最終目標(biāo)是整潔可用的代碼(clean code that works)。大多數(shù)的開(kāi)發(fā)者大部分時(shí)間無(wú)法得到整潔可用的代碼。辦法是分而治之。首先解決目標(biāo)中的“可用”問(wèn)題,然后再解決“代碼的整潔”問(wèn)題。這與體系結(jié)構(gòu)驅(qū)動(dòng)(architecture-driven)的開(kāi)發(fā)相反。

          采用TDD另一個(gè)好處就是讓我們擁有一套伴隨代碼產(chǎn)生的詳盡的自動(dòng)化測(cè)試集。將來(lái)無(wú)論出于任何原因(需求、重構(gòu)、性能改進(jìn))需要對(duì)代碼進(jìn)行維護(hù)時(shí),在這套測(cè)試集的驅(qū)動(dòng)下工作,我們代碼將會(huì)一直是健壯的。

          TDD的開(kāi)發(fā)周期

          Image [6].png

          添加一個(gè)測(cè)試 -> 運(yùn)行所有測(cè)試并檢查測(cè)試結(jié)果 -> 編寫(xiě)代碼以通過(guò)測(cè)試 -> 運(yùn)行所有測(cè)試且全部通過(guò) -> 重構(gòu)代碼,以消除重復(fù)設(shè)計(jì),優(yōu)化設(shè)計(jì)結(jié)構(gòu)

          兩個(gè)基本的原則

          • 僅在測(cè)試失敗時(shí)才編寫(xiě)代碼并且只編寫(xiě)剛好使測(cè)試通過(guò)的代碼

          • 編寫(xiě)下一個(gè)測(cè)試之前消除現(xiàn)有的重復(fù)設(shè)計(jì),優(yōu)化設(shè)計(jì)結(jié)構(gòu)

          關(guān)注點(diǎn)分離是這兩條規(guī)則隱含的另一個(gè)非常重要的原則。其表達(dá)的含義指在編碼階段先達(dá)到代碼“可用”的目標(biāo),在重構(gòu)階段再追求“整潔”目標(biāo),每次只關(guān)注一件事!

          分層測(cè)試點(diǎn)

          參考資料

          • 重構(gòu)-改善既有代碼的設(shè)計(jì)
          • 設(shè)計(jì)模式
          • Effective Java
          • 敏捷軟件開(kāi)發(fā)與設(shè)計(jì)的最佳實(shí)踐
          • 實(shí)現(xiàn)模式
          • 測(cè)試驅(qū)動(dòng)開(kāi)發(fā)

          干貨分享

          最近將個(gè)人學(xué)習(xí)筆記整理成冊(cè),使用PDF分享。關(guān)注我,回復(fù)如下代碼,即可獲得百度盤(pán)地址,無(wú)套路領(lǐng)取!

          ?001:《Java并發(fā)與高并發(fā)解決方案》學(xué)習(xí)筆記;?002:《深入JVM內(nèi)核——原理、診斷與優(yōu)化》學(xué)習(xí)筆記;?003:《Java面試寶典》?004:《Docker開(kāi)源書(shū)》?005:《Kubernetes開(kāi)源書(shū)》?006:《DDD速成(領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)速成)》?007:全部?008:加技術(shù)群討論

          加個(gè)關(guān)注不迷路

          喜歡就點(diǎn)個(gè)"在看"唄^_^

          瀏覽 56
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    奶水饱胀一区二区三区 | 免费 69视频 | 影音先锋你懂得 | 中文无码一区二区三区 | 91射在线观看 |