中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁(yè) > news >正文

企業(yè)咨詢師資格證百度seo 優(yōu)化

企業(yè)咨詢師資格證,百度seo 優(yōu)化,青海西寧高端網(wǎng)站建設(shè),泰山區(qū)疫情最新情況文章目錄 一、前置1.1 目的1.2 面向?qū)ο?.3 接口和抽象類 二、七大設(shè)計(jì)原則2.1 單一職責(zé)2.2 接口隔離原則2.3 依賴倒轉(zhuǎn)原則2.4 里氏替換原則2.5 開(kāi)閉原則2.6 不要重復(fù)原則2.7 迪米特最少知道法則 三、23種設(shè)計(jì)模式3.1創(chuàng)建型:創(chuàng)建對(duì)象3.1.1 單例模式定義最佳實(shí)踐場(chǎng)景…

文章目錄

  • 一、前置
    • 1.1 目的
    • 1.2 面向?qū)ο?/li>
    • 1.3 接口和抽象類
  • 二、七大設(shè)計(jì)原則
    • 2.1 單一職責(zé)
    • 2.2 接口隔離原則
    • 2.3 依賴倒轉(zhuǎn)原則
    • 2.4 里氏替換原則
    • 2.5 開(kāi)閉原則
    • 2.6 不要重復(fù)原則
    • 2.7 迪米特最少知道法則
  • 三、23種設(shè)計(jì)模式
    • 3.1創(chuàng)建型:創(chuàng)建對(duì)象
      • 3.1.1 單例模式
          • 定義
          • 最佳實(shí)踐
          • 場(chǎng)景
          • 線程級(jí)別的單例
          • 缺點(diǎn)
      • 3.1.2 工廠模式(簡(jiǎn)單工廠)
          • 場(chǎng)景1:解析配置
          • 場(chǎng)景2:解析表達(dá)式
          • 實(shí)戰(zhàn)-Calendar類
          • 總結(jié):
      • 3.1.3DI依賴注入
          • 定義
          • 和簡(jiǎn)單工廠區(qū)別
          • 手動(dòng)實(shí)現(xiàn)一個(gè)DI容器
            • 使用
            • 1、配置解析
            • 2、BeanFactory通過(guò)反射創(chuàng)建對(duì)象
            • 3、對(duì)象生命周期管理
      • 3.1.4 Builder建造者模式
        • 和set以及構(gòu)造器的區(qū)別
        • 建造者模式創(chuàng)建對(duì)象
        • 實(shí)戰(zhàn)@Accessors
        • 和工廠模式的區(qū)別
    • 3.2結(jié)構(gòu)型:類或?qū)ο蟮慕M合
      • 3.2.1Proxy代理模式
        • 定義
        • 場(chǎng)景
        • 動(dòng)態(tài)代理接口實(shí)現(xiàn)
      • 3.2.2 裝飾器模式
        • 作用
        • 和代理模式的區(qū)別
        • 實(shí)現(xiàn)
        • 場(chǎng)景IO流
          • 1、結(jié)構(gòu)
          • 2、源碼結(jié)構(gòu)
          • 3、前置背景
          • 4、源碼解析
      • 3.2.3 適配器模式
        • 定義
        • 實(shí)現(xiàn)
          • 1、類適配器-繼承
          • 2、對(duì)象適配器-組合
        • 場(chǎng)景
          • 1、兼容老接口
          • 2、統(tǒng)一多個(gè)類的接口設(shè)計(jì)
      • 3.2.4 享元模式
        • 定義
        • 作用
        • 實(shí)戰(zhàn)
        • 場(chǎng)景
          • Integer實(shí)戰(zhàn)
        • 總結(jié)
    • 3.3行為型
      • 3.3.1 觀察者模式
        • 定義
        • 作用
        • 和生產(chǎn)者消費(fèi)者的區(qū)別
        • 和 訂閱-發(fā)布模式的區(qū)別
        • 被觀察者-觀察者模板
        • 實(shí)戰(zhàn)
      • 3.3.2 模板模式
        • 定義
        • 作用
        • 使用模板
        • 場(chǎng)景
          • 1、復(fù)用
          • 2、擴(kuò)展框架
          • 3、實(shí)戰(zhàn)
      • 3.3.3回調(diào)函數(shù)
        • 定義
        • 作用
        • 場(chǎng)景1
          • 1、背景JDBCTemplate的演變
          • 2、源碼解析JDBCTemplate(簡(jiǎn)化版)
          • 3、分析
        • 場(chǎng)景2 JVM Hook
      • 3.3.4策略模式:
        • 實(shí)戰(zhàn)1
          • 背景
        • 實(shí)戰(zhàn)2
        • 實(shí)戰(zhàn)3
      • 3.3.5 職責(zé)鏈模式
        • 定義
        • 作用
        • 模板
          • 1、職責(zé)鏈模式1:帶終止
          • 2、職責(zé)鏈模式2:無(wú)終止
        • 實(shí)戰(zhàn):敏感詞過(guò)濾
        • 場(chǎng)景1:Servlet-Filter
          • 作用
          • 作用域
          • 解析
        • 場(chǎng)景2:MVC-Interceptor
          • 作用:同F(xiàn)ilter
          • 作用域
          • 和Servlet的Filter區(qū)別
          • 解析
        • 場(chǎng)景3:自定義職責(zé)鏈
      • 3.3.6 狀態(tài)模式
        • 定義
        • 作用
        • 使用場(chǎng)景
        • 實(shí)戰(zhàn)-電商訂單
          • 1、狀態(tài)機(jī)
          • 2、實(shí)現(xiàn)
          • 3、總結(jié)
      • 3.3.7 解釋器模式
        • 定義
        • 作用
        • 實(shí)戰(zhàn)
          • 背景
          • 分析
          • 實(shí)現(xiàn)
        • 場(chǎng)景
      • 3.3.8 中介模式
        • 定義
        • 作用
        • 場(chǎng)景
          • 背景
          • 中介模式
  • 四、其它
    • 4.1 系統(tǒng)設(shè)計(jì)
      • 4.1.1合理將功能劃分到不同模塊
      • 4.1.2模塊之間的交互關(guān)系
      • 4.1.3業(yè)務(wù)開(kāi)發(fā)
    • 4.2 重構(gòu)
      • 4.2.1what
      • 4.1.2why
      • 4.1.3 context
      • 4.1.4 when
      • 4.1.5 如何避免重構(gòu)出問(wèn)題
    • 4.2 重構(gòu)
      • 4.2.1what
      • 4.2.2why
      • 4.2.3 context
      • 4.2.4 when
      • 4.2.5 如何避免重構(gòu)出問(wèn)題

一、前置

1.1 目的

1、寫出好的代碼,個(gè)人認(rèn)為依次重要程度為:

  • 健壯性

    • 個(gè)人理解為最重要的之一,好的代碼,首先是無(wú)bug代碼

    • 代碼中,常見(jiàn)可能引起問(wèn)題的點(diǎn)(重要程度不分先后):

      • 性能:并發(fā)會(huì)不會(huì)存在問(wèn)題;并發(fā)度大小(大了可能對(duì)下游壓力過(guò)大,提前對(duì)其好SLA);是否需要異步處理(寫OP日志、推送消息等)

      • 降級(jí)、兜底:下游接口拿不到數(shù)據(jù)或不可用,產(chǎn)品側(cè)是否有兜底數(shù)據(jù)、技術(shù)側(cè)是否有兜底方案

      • 限流:對(duì)上游是否需要限流,保護(hù)我們的服務(wù)

      • 數(shù)據(jù)量評(píng)估:數(shù)據(jù)量增大,甚至極端場(chǎng)景下,會(huì)不會(huì)有慢查詢,索引是否合理

      • 冪等:消息是否可能重復(fù)

      • 一致性:上下游的狀態(tài)是否一致(eg:服務(wù)A將任務(wù)狀態(tài)置為終態(tài),如果上游系統(tǒng)B有業(yè)務(wù)動(dòng)作依賴A任務(wù)的狀態(tài),那A也要告訴上游B任務(wù)終態(tài)了。否則上游B發(fā)現(xiàn)下游A的任務(wù)一直未終態(tài),他們可能有自己的重試機(jī)制等)

      • 主從延時(shí):寫完讀

      • 接口異常:是否強(qiáng)依賴、重試等

      • 中間件異常:是否強(qiáng)依賴redis,如果redis短期不可用,業(yè)務(wù)上是否可以降級(jí)跳過(guò)redis的卡控邏輯。使用中間件生成單據(jù)號(hào),如果中間件短時(shí)間不可用,是否有備用方案生成單據(jù)號(hào)等

      • 邊界條件:while循環(huán)為了防止死循環(huán),結(jié)合業(yè)務(wù)要設(shè)置最大的循環(huán)次數(shù);終止條件最好是>=或<=,防止并發(fā)時(shí)跳過(guò)了;日期判斷或者日期作為查詢條件也要特別注意;集合get(0)首先是npe其次是集合的所有元素是否都一致,不一致就不能拿第一個(gè)元素的內(nèi)容去賦值;switch要有default;if要和else if() else if()最好加上條件,避免落到if else中

      • 數(shù)據(jù)庫(kù):字段類型(是否大小寫敏感)、大小(是否需要截?cái)?#xff09;、update是否需要updateSelective、查詢in(為空)則可能查詢?nèi)繑?shù)據(jù)

      • 參數(shù)校驗(yàn):api接口一定不要相信傳參

      • 事務(wù)失效問(wèn)題:rpc寫和本地寫、以及其它

      • 單位問(wèn)題:精度、元分。kg和mg等

      • npe問(wèn)題:常見(jiàn)可能造成npe的點(diǎn)

      • 鎖的釋放:超時(shí)時(shí)間是否設(shè)定、異常流程是否釋放鎖

      • /0

      • list轉(zhuǎn)map,list的字段可能重復(fù),作為map的key則可能Duplicate key異常

  • 可讀性

    • 代碼終究是給人讀的
    • 主要是代碼的整潔之道那些內(nèi)容
  • 可復(fù)用性

    • 不寫重復(fù)的代碼
    • 高內(nèi)聚低耦合(模塊內(nèi)部?jī)?nèi)聚,模塊之間解耦)
    • eg:網(wǎng)關(guān)層封裝的查詢方法應(yīng)該做的事項(xiàng)有:
      封裝并發(fā)(調(diào)用方只需要傳入所有的skuIdList,gateway自己按照200、200拆分sku并發(fā)查詢)
      返回?cái)?shù)據(jù)要自定義(如果rpc調(diào)用的返回對(duì)象為集合List其中A有很多的屬性,可能我們并不關(guān)心,那么gateway就需要自己定義對(duì)象DTO,屬性只有幾個(gè)我們關(guān)心的字段即可)
  • 可擴(kuò)展性

    • 設(shè)計(jì)模式的內(nèi)容
    • eg:需求需要對(duì)規(guī)則進(jìn)行新增、刪除,只需要調(diào)整枚舉類,不需要修改現(xiàn)有代碼邏輯
  • 兼容性:尤其是字段調(diào)整,要遵循新增而非刪除(eg:skuId變?yōu)閟kuIdList,一般是新增字段,然后做好上線過(guò)度)

2、設(shè)計(jì)原則、設(shè)計(jì)模式等,目的都是為了寫“好”代碼

1.2 面向?qū)ο?/h3>

1、看似面向?qū)ο?#xff0c;實(shí)則面向過(guò)程的做法

  • 濫用get、set方法,違反了面向?qū)ο蟮奶卣?#xff1a;封裝。除非需要否則,不要給屬性定義setter

  • Constants常量類:不要單獨(dú)設(shè)計(jì)此常量類。好的 做法:哪個(gè)類用的用到了某個(gè)常量,在此類匯總定義即可

    否則,不易維護(hù):改一個(gè)常量,影響太多地方,不能輕易修改;不易復(fù)用:要在另一個(gè)項(xiàng)目中復(fù)用本項(xiàng)目的某類,此類中又依賴Constants,相當(dāng)于把整個(gè)Constants都一并引入了

2、面向?qū)ο缶幊滩襟E

以:對(duì)接口進(jìn)行鑒權(quán)為例

  • 分析實(shí)現(xiàn)步驟

    • 調(diào)用方進(jìn)行接口請(qǐng)求的時(shí),將URL、useId、pwd、ts時(shí)間戳拼接在一起傳遞過(guò)來(lái);通過(guò)加密生成token;并將token、useId、ts拼接在URL中傳遞
    • 接收到請(qǐng)求后,解析URL,獲取token、useId、ts
    • 校驗(yàn)ts和當(dāng)前時(shí)間,是否在合理的時(shí)間窗口內(nèi)(生成的ts和當(dāng)前時(shí)間間隔1min則認(rèn)為token失效),失效則拒絕
    • 通過(guò)useId去緩存或db中獲取pwd,通過(guò)同樣的方式生成token,與調(diào)用傳遞的token對(duì)比,不一致則拒絕
  • 劃分職責(zé),識(shí)別出有哪些類

    • 如果是大需求,涉及多個(gè)模塊,則需要先把需求按照模塊劃分。eg:逆向計(jì)劃自動(dòng)建單分為(觸發(fā)模塊、獲取可退sku、計(jì)算可退量、合單、下發(fā)、回掉等多個(gè)模塊)

    • 將需求轉(zhuǎn)換為每個(gè)模塊要實(shí)現(xiàn)的內(nèi)容;并拆解為小的功能,一條一條列出來(lái),這里以接口鑒權(quán)為例

      1、把URL、useId、pwd、ts拼接為子串

      2、通過(guò)字符串,加密生成token

      3、將useId、token、ts形成新的url

      4、解析url,獲取ts、useId、token

      5、根據(jù)useId去存儲(chǔ)介質(zhì)中獲取pwd

      6、根據(jù)ts判斷token是否在有效的窗口內(nèi)

      7、根據(jù)獲取的pwd同樣方式生成token,比較和傳遞過(guò)來(lái)的token是否一致

    • 其中1、2、6、7和token相關(guān),負(fù)責(zé)token的生成和比對(duì) ; 3、4和URL相關(guān),負(fù)責(zé)url的拼接、解析等;5是單獨(dú)的獲取pwd。

      這樣,我們就定義了三個(gè)主要的類:AuthToken、Url、UseStorage

    • 這里體現(xiàn)了高內(nèi)聚(將小的功能點(diǎn)理清楚到底屬于哪個(gè)類,相同的都放在一起),低耦合(不屬于這個(gè)類的屬性和方法,則不要放在這個(gè)類里,比如URL信息,useId不應(yīng)該屬于Token,不要作為他的屬性)

  • 定義類 和 屬性、方法

    • AuthToken:定義屬性和方法:

      • 1和2:createToken(String url, Long useId,String pwd)
      • 6:isExpireed(Long ts)
      • 7:match(String token)
    • ApiUrl

      • 3:buildUrl()

      • 4:getTokenFromUrl(String url)

        :getUseIdFromUrl(String url)

        :getTsFromUrl(String url)

  • 定義類和類之間的交互關(guān)系(繼承、實(shí)現(xiàn)、聚合、組合、依賴等)

  • 思考:

    我理解的面向?qū)ο缶幊?#xff0c;就好比要外出旅游,將這個(gè)需求分為:衣食住行四個(gè)模塊

    • 衣:帶什么衣服(上衣、褲子等)
    • 行:是坐火車還是飛機(jī),如果是坐火車,如何去火車站等
    • 住:是住酒店還是民宿,住的地方和旅游景點(diǎn)的遠(yuǎn)近、交通的便利等

    就是在未出發(fā)之前,衣食住行模塊都想好,方法也想好(先公交、再火車),類之間如何銜接(對(duì)應(yīng)類之間的關(guān)系)。然后按照這些去旅游。

    面向過(guò)程編程,則是準(zhǔn)備去旅游。

    • 行模塊:早上起來(lái)看有飛機(jī)航班么,沒(méi)有則坐火車,最近的一趟火車,出發(fā)。
    • 住:到了目的地,隨便找個(gè)地方先住下來(lái)

    類似這種,我理解為面向過(guò)程。

1.3 接口和抽象類

1、什么時(shí)候使用接口

  • 需要將接口和實(shí)現(xiàn)相分離,封住不穩(wěn)定的實(shí)現(xiàn),暴露穩(wěn)定的接口
  • 上游系統(tǒng)面向接口編程,這樣接口實(shí)現(xiàn)發(fā)生變化時(shí),上游系統(tǒng)代碼基本不需要改動(dòng)。降低了代碼的耦合性
  • 提升了擴(kuò)展性

2、要用接口和抽象類時(shí),選擇哪個(gè)

  • 要表示is a(三角形是圖形,圓形是圖形—),同時(shí)目的是為了解決代碼的復(fù)用性,則使用抽象類;
  • 表示has a,并且為了解耦,而非代碼的復(fù)用,則使用接口

3、基于接口編程注意事項(xiàng)

  • 函數(shù)名不要暴露實(shí)現(xiàn)細(xì)節(jié),否則后續(xù)需求變化,名稱可能詞不達(dá)意甚至描述有誤。所以盡量抽象。eg:uploadPicture而非uploadPicture2Yun
  • 封裝具體的實(shí)現(xiàn)細(xì)節(jié)。

eg:sku查詢算法值(不同的sku對(duì)應(yīng)的供貨鏈路不同,不同的供貨鏈路,對(duì)應(yīng)查詢不同的算法類型值),則queryAlQty(Integer supplyType,Long skuId)不如

queryAlQty(Long skuId):內(nèi)部封裝了查詢供貨鏈路。

二、七大設(shè)計(jì)原則

(設(shè)計(jì)模式本身的原則)

2.1 單一職責(zé)

1、概念:一個(gè)類只負(fù)責(zé)一項(xiàng)職責(zé)。如果負(fù)責(zé)了多個(gè),就需要拆分成多個(gè)類

2、舉例:OrderRepository中不要涉及對(duì)SkuDO的CRUD

3、作用:

  • 不會(huì)使一個(gè)類過(guò)于龐大;
  • 可維護(hù)性:改了Order相關(guān)內(nèi)容不會(huì)影響Sku相關(guān)內(nèi)容,否則可能會(huì)相影響;
  • 高內(nèi)聚(提高代碼,縮小功能改動(dòng)導(dǎo)致的代碼改動(dòng)范圍)。

4、編碼實(shí)現(xiàn)

  • 不符合單一職責(zé)的代碼

原因:顯然飛機(jī)不能一直在公路上跑。應(yīng)該拆分為陸、海、空三個(gè)單一職責(zé)的交通工具類

@Data
public class Single {public static void main(String[] args) {Vehicle vehicle = new Vehicle();vehicle.run("汽車");vehicle.run("飛機(jī)");}static class Vehicle{public void run(String vehicle) {System.out.println(vehicle + "一直在公路上跑");}}
}
  • 符合單一職責(zé)的類
@Data
public class Single {public static void main(String[] args) {Vehicle1 vehicle1 = new Vehicle1();vehicle1.run("汽車");Vehicle2 vehicle2 = new Vehicle2();vehicle2.run("飛機(jī)");}static class VehicleRoad{public void run(String vehicle) {System.out.println(vehicle + "在公路上跑");}}static class VehicleAir{public void run(String vehicle) {System.out.println(vehicle + "在天上非");}}
}

5、思考

逆序計(jì)劃流程 = 1觸發(fā)建單 + 2【觸發(fā)oih + 落sku + 計(jì)算可退量 + 合單并下發(fā)+回掉】

做了RDC退、協(xié)同退、PC退之后,發(fā)現(xiàn)流程2是完成可以復(fù)用。但是流程1,不同的觸發(fā)源尤其是RDC退和PC退,很多代碼都寫在一個(gè)類中,實(shí)際上違背了單一職責(zé)。改動(dòng)PC退的流程1代碼有可能影響RDC退。

6、如何定義一個(gè)類,以及如何根據(jù)單一職責(zé),判斷一個(gè)類是否需要拆分

public class UserInfo {private Long userId;private String name;private Long createTime;private Long lastLoginTime;private String email;private Long phoneNo;private String province;private String city;private String region;private String detailAddress;
}
  • 可以先第一版比較粗的類UserInfo。隨著業(yè)務(wù)迭代持續(xù)重構(gòu):比如后續(xù)有了物流業(yè)務(wù),則用戶的地址信息可以抽取出來(lái)獨(dú)立類;

    比如:后續(xù)有了論壇、金融等業(yè)務(wù)需要對(duì)用戶進(jìn)行登錄校驗(yàn),則可以將email、phone拆出來(lái)獨(dú)立類

  • 代碼屬性過(guò)多、代碼的行數(shù)過(guò)多(>200)、代碼的方法過(guò)多,則需要考慮是否對(duì)類進(jìn)行拆分

  • 依賴的其它類過(guò)多。為了低耦合,考慮是否拆

  • 私有方法過(guò)多,為了復(fù)用性,可以抽取出來(lái)放到新類中作為public方法

  • 類已經(jīng)找不到合適的詞來(lái)形容了,職責(zé)定義已經(jīng)不清晰了,可拆

  • 類中大量的方法都在對(duì)某幾個(gè)屬性進(jìn)行操作,則可以考慮將這幾個(gè)屬性抽取出來(lái)單獨(dú)成一個(gè)類

2.2 接口隔離原則

1、概念:接口的調(diào)用者,不應(yīng)該被強(qiáng)迫依賴它不需要的接口

2、作用:

  • 提高靈活性:一個(gè)類是可以同時(shí)實(shí)現(xiàn)多個(gè)接口的,所以將一個(gè)臃腫的接口分割為若干個(gè)小接口,通過(guò)小接口的不同組合可以滿足更多的需求
  • 高內(nèi)聚

3、滿足接口隔離原則code

  • "接口"含義:一個(gè)接口中的多個(gè)方法

    • 不滿足接口隔離

      public interface UserService{boolean register(String phone, String pwd);boolean login(String phone, String pwd);UserInfo getUserInfo(String phone);boolean deleteUser(String phone, String pwd);//刪除用戶
      }public UserServiceImpl implements UserService{//---
      }
      

      正常情況下,用戶在調(diào)用UserService接口中的方法時(shí),一般不會(huì)也不允許調(diào)用deleteUser方法,只會(huì)用到CRU功能。

      根據(jù)接口隔離原則:接口的調(diào)用者,不應(yīng)該強(qiáng)迫依賴他不需要的接口即deleteUser方法

    • 滿足接口隔離

      后端管理系統(tǒng)ManagerUserImpl才需要CRUD功能

      public interface UserService{boolean register(String phone, String pwd);boolean login(String phone, String pwd);UserInfo getUserInfo(String phone);
      }public interface ManagerService{boolean deleteUser(String phone, String pwd);//刪除用戶
      }
      
      public class UserServiceImpl implements UserService{//CRU功能---
      }public class ManagerServiceImpl implements UserService, ManagerService{//CRUD功能---
      }
      
  • “接口”的含義:可以是接口中的某個(gè)方法

    • 不滿足接口隔離

      public class Statistics {private Long max;private Long min;private double avg;private Integer count;public Statistics count(Collection data) {Statistics statistics = new Statistics();// 計(jì)算邏輯return statistics;}
      }
      

      count函數(shù)功能不單一,包含了max、min、count、avg等多個(gè)功能。

      按照接口隔離原則:函數(shù)的設(shè)計(jì)功能單一,不要將多個(gè)不同的功能邏輯在一個(gè)函數(shù)中

    • 滿足接口隔離

      將count方法拆分為max()、min()、avg()等方法。如何要想使用復(fù)合計(jì)算則可以直接使用

      LongSummaryStatistics statistics = new LongSummaryStatistics();
      statistics.accept(1);
      statistics.accept(2);
      statistics.accept(3);
      

2.3 依賴倒轉(zhuǎn)原則

1、概念:高層模塊(調(diào)用者)不要依賴低層模塊(被調(diào)用者),二者應(yīng)該通過(guò)抽象(接口)互相依賴

eg:Tomcat:高層模塊,編程的Web應(yīng)用程序(低層模塊)只需要部署在Tomcat容器下,便可以被Tomcat調(diào)用運(yùn)行。

Tomcat不依賴Web應(yīng)用程序,只要Web應(yīng)用程序滿足Servlet接口規(guī)范,那么無(wú)論你是啥Web應(yīng)用程序,都可以在Tomcat上運(yùn)行。

Tomcat和Web應(yīng)用程序通過(guò)Servlet接口互相依賴

2、作用:通用性好、擴(kuò)展性好

3、控制翻轉(zhuǎn)IOC

  • 定義:原本是程序員自己控制整個(gè)程序的執(zhí)行,使用框架之后,框架來(lái)控制程序流程。流程的控制權(quán)從程序員反轉(zhuǎn)到了框架

  • 舉例:

    • 程序員控制程序執(zhí)行

      public class UserServiceTest {public static boolean needTest() {return true;}public static void main(String[] args) {if (needTest()) {System.out.println("do test");} else {System.out.println("not do test");}}
      }public class SkuServiceTest {public static boolean needTest() {return false;}public static void main(String[] args) {if (needTest()) {System.out.println("do test");} else {System.out.println("not do test");}}
      }
      
    • 框架控制程序執(zhí)行

      //這里類似模板設(shè)計(jì)
      public abstract class BaseTest {public boolean needTest();//預(yù)留擴(kuò)展點(diǎn)public void run() {if (needTest()) {System.out.println("do test");} else {System.out.println("not do test");}}
      }
      
      public class UserServiceTest extends BaseTest{@Overridepublic static boolean needTest() {return true;}
      }public class SkuServiceTest extends BaseTest{@Overridepublic static boolean needTest() {return false;}
      }
      
      public class ApplicationLoader {public static void main(String[] args) {SpringApplication.run(ApplicationLoader.class, args);private static final List<BaseTest> LIST = new ArrayList<>();for (BaseTest test : LIST) {test.run()}}public void register(BaseTest test) {LIST.add(test);}
      }
      
ApplicationLoader.register(new UserServiceTest());
ApplicationLoader.register(new UserServiceTest());1、在BaseTest預(yù)約擴(kuò)展點(diǎn)
2、不同的Test類,實(shí)現(xiàn)自己業(yè)務(wù)相關(guān)的功能(是否needTest),不需要再寫用于執(zhí)行流程的main函數(shù)了
3、將不同的Test類,添加到ApplicationLoader
4、在ApplicationLoader啟動(dòng)的時(shí)候執(zhí)行main函數(shù),會(huì)遍歷執(zhí)行所有Test的run方法
程序的執(zhí)行(main函數(shù)執(zhí)行),由程序員控制(寫在不同Test中),反轉(zhuǎn)到框架控制(統(tǒng)一register到Application,它啟動(dòng)的時(shí)候,會(huì)執(zhí)行所有Test的run方法)

4、依賴注入DI

  • 定義:

    A類中使用B類,不同new B()的方法創(chuàng)建b,而是將B在外部創(chuàng)建好后,通過(guò)new A(b)構(gòu)造函數(shù)、函數(shù)參數(shù)func(B b)、set屬性等方式傳遞(注入)給A類使用

  • 和控制反轉(zhuǎn)的關(guān)系:

    控制反轉(zhuǎn)不是具體的實(shí)現(xiàn)技巧,而是一種用于指導(dǎo)框架設(shè)計(jì)的思想。而DI則是具體的編碼技巧,是IOC的具體實(shí)現(xiàn)

  • 依賴注入 和 非依賴注入

    • 背景:

    Notification類負(fù)責(zé)將商品的促銷、驗(yàn)證碼消息等給用戶。它依賴MessageProductor生產(chǎn)者類發(fā)送消息

    • 非依賴注入
      • B類(MessageProductor)
    public class MessgaeProductor {public boolean send(String msg) {//}
    }
    

    ? A類(Notification)

    public class Notification {private MessgaeProductor messgaeProductor;public Notification() {this.messgaeProductor = new MessgaeProductor();//A類中使用B類,通過(guò)new方式在A類中創(chuàng)建}public void sendMessage(String msg) {this.messgaeProductor.send(msg);}
    }
    
    Notification notification = new Notification();
    notification.sendMessage("msg");
    
    • 依賴注入

      • B1、B2(MessgaeProductor接口實(shí)現(xiàn)類)
      public interface MessgaeProductor {public boolean send(String msg);
      }
      
      // B1:短信生產(chǎn)類
      public class SmsProductor implements MessgaeProductor{@Overridepublic boolean send(String msg) {//發(fā)送短信}
      }// B2:微信消息生產(chǎn)類
      public class WeChatProductor implements MessgaeProductor{@Overridepublic boolean send(String msg) {//發(fā)送微信信息}
      }
      
      • A(Notification類)
      public class Notification {private MessgaeProductor messgaeProductor;public Notification(MessgaeProductor messgaeProductor) {this.messgaeProductor = messgaeProductor;//A類中使用B類,通過(guò)構(gòu)造器將b注入A中}public void sendMessage(String msg) {this.messgaeProductor.send(msg);}
      }
      
      • 外部
      public class Demo {public static void main(String[] args) {DaxiangProductor messgaeProductor = new DaxiangProductor();//創(chuàng)建對(duì)象bNotification notification = new Notification(messgaeProductor);//通過(guò)構(gòu)造函數(shù),將b依賴注入A類中notification.sendMessage("msg");}
      }
      

5、依賴注入框架

  • 產(chǎn)生背景

    • 對(duì)比依賴注入和非依賴注入,發(fā)現(xiàn)new B()的動(dòng)作,只不過(guò)從在A類中new,變成了在更上次外部類Demo中new,還是需要程序員自己實(shí)現(xiàn)
    • 一個(gè)項(xiàng)目可能有成百上千個(gè)類的創(chuàng)建和依賴注入,如果全部都由程序員自己實(shí)現(xiàn),將變得復(fù)雜容易出錯(cuò)
    • 對(duì)象的創(chuàng)建和依賴注入動(dòng)作本身和業(yè)務(wù)不相關(guān),完全可以抽象成框架來(lái)自動(dòng)完成
  • 常見(jiàn)的依賴注入框架:Spring、Google的Guice

  • 作用

    • 參考背景
    • 只需要通過(guò)依賴注入框架提供的擴(kuò)展點(diǎn),簡(jiǎn)單配置一下雖有需要?jiǎng)?chuàng)建的類對(duì)象、類與類之間的依賴關(guān)系,就可以實(shí)現(xiàn)由框架來(lái)自動(dòng)創(chuàng)建對(duì)象、管理對(duì)象的生命周期、依賴注入等
  • 舉例

    public class A{@Resourceprivate B b;public static void main(String[] args) {b.send("msg");}
    }
    

通過(guò)Spring框架提供的擴(kuò)展點(diǎn)-后置處理器,@Resource private B b,就可以實(shí)現(xiàn)B的創(chuàng)建和生命周期的管理,同時(shí)后置處理器通過(guò)set的將b注入A類中

2.4 里氏替換原則

1、概念: 子類對(duì)象能夠替換程序中父類對(duì)象出現(xiàn)的任何地方,并且保證原來(lái)的邏輯行為不變且正確性不被破壞。

一句話:子類重寫父類的方法,不要改變?cè)蟹椒ǖ倪壿?#xff08;方法聲明、輸入、輸出、對(duì)異常的處理等約定)

2、作用:指導(dǎo)子類如何設(shè)計(jì),不改變父類的邏輯

3、子類重寫父類方法時(shí),常見(jiàn)的違背里氏替換原則的場(chǎng)景有

  • 違背父類的輸入

    父類輸入Integer是整數(shù),子類輸入Integer要求是正整數(shù)

  • 違背父類的輸出

    父類catch代碼塊中return的是空集合,子類重寫方法中catch塊中return的是null

  • 違背父類方法的聲明

    父類sortBySkuId,查詢結(jié)果按照skuId排序。子類sortBySkuId查詢結(jié)果按照實(shí)時(shí)銷量排序了

  • 違背父類異常的處理

    父類valid參數(shù)時(shí),不滿足時(shí),拋出的是ArguementNullException。子類拋出的是illeagalException

4、思考:

2.5 開(kāi)閉原則

1、概念:一個(gè)模塊、類、方法對(duì)修改關(guān)閉,對(duì)擴(kuò)展開(kāi)放。添加一個(gè)新功能,應(yīng)該是新增代碼,而非 修改代碼。

補(bǔ)充:有的時(shí)候新增功能,是改變了類,對(duì)于類而言是被改變了,但是對(duì)于方法來(lái)說(shuō)沒(méi)有改變,也滿足開(kāi)閉。

2、作用:提升代碼的擴(kuò)展性,23種設(shè)計(jì)模式的目的都是為了滿足開(kāi)閉原則。盡量讓修改更為集中、給小、更上層。核心的、復(fù)雜的邏輯盡量不修改少修改

3、編碼:可以參考RuleExpressHelper,通過(guò)遍歷規(guī)則枚舉類,將枚舉類code、desc提前put至Map<String,Integer> map1,Map<Integer,String> map2,其內(nèi)部提供了將"lo&&s"轉(zhuǎn)換lo、s,再通過(guò)map1轉(zhuǎn)換為[1,2],將[1,2]轉(zhuǎn)換為"lo&&s",將"lo&&s"轉(zhuǎn)換為[“小于OR可以修改”,“鎖庫(kù)不可更改”]等,規(guī)則表示之間的轉(zhuǎn)換方法。這樣新增規(guī)則時(shí),只需要對(duì)應(yīng)新增枚舉類即可

4、如何做到滿足開(kāi)閉原則

  • 業(yè)務(wù)層面:擴(kuò)展意識(shí)、抽象意識(shí)很重要

    • 多想下,這塊邏輯,后續(xù)會(huì)有哪些需求變更,設(shè)計(jì)代碼結(jié)構(gòu)的時(shí)候,可以提前預(yù)留好擴(kuò)展點(diǎn),以便將來(lái)改動(dòng)小,新的代碼可靈活的插入

      eg1:規(guī)則中心,現(xiàn)有n個(gè)規(guī)則,如果后續(xù)新增規(guī)則,是不是不改變現(xiàn)有代碼邏輯實(shí)現(xiàn),僅僅通過(guò)新增枚舉類就可以實(shí)現(xiàn)頁(yè)面的CRUD。

      eg2:退供下發(fā)執(zhí)行,后續(xù)會(huì)不會(huì)有逆向調(diào)撥下發(fā)執(zhí)行。可以提前設(shè)計(jì)下發(fā)邏輯,抽象出接口。

    • 但是對(duì)于未來(lái)不確定的功能點(diǎn),當(dāng)下沒(méi)必要過(guò)度設(shè)計(jì),后續(xù)持續(xù)重構(gòu)即可

  • 技術(shù)層面:提升代碼的可擴(kuò)展性即基于接口編程、設(shè)計(jì)原則、設(shè)計(jì)模式(策略、模板、狀態(tài)、裝飾著、職責(zé)鏈等)

    感觸最深的就是策略模式,定義行為(接口方法),新增功能,只需要新增對(duì)應(yīng)的新實(shí)現(xiàn),不需要改動(dòng)原本的行為實(shí)現(xiàn)

2.6 不要重復(fù)原則

1、常見(jiàn)重復(fù)場(chǎng)景

  • 實(shí)現(xiàn)邏輯重復(fù):代碼完全一樣。

    eg:可能是不同的人開(kāi)發(fā),不知道有這個(gè)功能的代碼,場(chǎng)景的是枚舉定義一樣、網(wǎng)關(guān)定義一樣

  • 功能語(yǔ)義重復(fù)

    • 代碼不一樣了,但是兩個(gè)函數(shù)是一樣功能。

      eg:checkAddressIsVali()和isValidAddress()

    • 同一個(gè)功能的枚舉類,定義了多個(gè)。

      eg:逆向計(jì)劃中,任務(wù)的觸發(fā)源類型:OriginTypeEnum 和 TriggerSourceEnum。這樣以后枚舉內(nèi)容修改了,多處都要修改,否則有問(wèn)題

  • 代碼重復(fù)執(zhí)行

    已經(jīng)在request中校驗(yàn)了poiId不能為null,又在構(gòu)造criteria的時(shí)候,再次校驗(yàn)if(request.getPoiId() != null)

    對(duì)于這種情況,個(gè)人建議是可以多次校驗(yàn)的,因?yàn)椴慌懦程?#xff0c;入?yún)equest中允許這個(gè)字段為null了

2、如何提升代碼的復(fù)用性

  • 高內(nèi)聚、低耦合

    大而全的類,依賴它的代碼就多。進(jìn)而增加了代碼的耦合度,影響代碼的復(fù)用。粒度越小的代碼,通用性越好(DateUtil中)。越容易被復(fù)用

  • 業(yè)務(wù)和非業(yè)務(wù)邏輯分離

    越是和業(yè)務(wù)無(wú)關(guān)的代碼,越容易復(fù)用。

    eg:生成單據(jù)號(hào)、查詢倉(cāng)、品類、日期

  • 代碼下沉

    • 下沉的代碼盡量通用。

      eg:根據(jù)倉(cāng)id和skuIdList查詢sku信息,方法的內(nèi)部實(shí)現(xiàn)封裝了并發(fā)查詢邏輯。

  • 繼承、多態(tài)、抽象、封裝

    • 封裝:同上代碼下沉。即使后續(xù),下游rpc接口只允許sku 20個(gè)批量查詢,調(diào)用此查詢方法方也無(wú)需感知

    • 繼承:公共代碼抽取到父類,子類復(fù)用父類的方法和屬性。

      eg:模板模式,通用的都抽取到父類,不同的繼承,實(shí)現(xiàn)自己具體內(nèi)容

    • 多態(tài):使用多態(tài)可以動(dòng)態(tài)的復(fù)用一段代碼的部分邏輯。

      eg:Collection接口的通用方法,集合都可以使用

    • 抽象:越抽象、越不依賴具體實(shí)現(xiàn)的代碼,越容易復(fù)用

      eg:入?yún)長(zhǎng)ist,復(fù)用性高于ArrayList

      eg:send(HtmlRequest req),復(fù)用性不如send(String address, Byte[] data)。因?yàn)楹罄m(xù)數(shù)據(jù),可能服務(wù)于別的發(fā)送,不僅僅是html的發(fā)送

  • 復(fù)用意識(shí):

    • 設(shè)計(jì)一個(gè)方法的時(shí)候,要把它想象成類似于對(duì)外提供的API方法那樣的復(fù)用性(不同方、不同業(yè)務(wù)都可能會(huì)調(diào)用你)
    • 多思考,編寫的這部分代碼是否可以抽取出來(lái),作為一個(gè)獨(dú)立的方法提供給其他地方使用

2.7 迪米特最少知道法則

1、定義:一個(gè)對(duì)象應(yīng)該對(duì)其他對(duì)象有最少的了解,即最小知道?;蛑皇侵苯拥呐笥呀涣?/p>

  • 直接朋友:出現(xiàn)在類屬性、方法入?yún)⒑统鰠⒅械念?/li>
  • 間接朋友:出現(xiàn)在局部變量的類,和他們的交流(使用)就會(huì)違背迪米特法則

2、作用:低耦合、高內(nèi)聚

  • 高內(nèi)聚、單一職責(zé)原則:相近功能放在一個(gè)類中,不相近的功能不放在一個(gè)類中。相近的功能往往會(huì)被同時(shí)修改,這樣改動(dòng)點(diǎn)比較集中
  • 低耦合:類和類之間的關(guān)系簡(jiǎn)單清晰,一個(gè)類的改動(dòng),不會(huì)或很少會(huì)導(dǎo)致依賴它的類也需要跟著改動(dòng)
    • eg:基于接口編程,接口內(nèi)部實(shí)現(xiàn)類變化了,但是對(duì)外提供的api不會(huì)變
    • eg:接口隔離,接口被拆為更細(xì)化的接口。不拆分之前接口影響多個(gè)依賴方,拆分成多個(gè)更細(xì)的接口后,某個(gè)點(diǎn)-對(duì)應(yīng)更細(xì)接口的變動(dòng),影響的依賴方更少

3、代碼:

  • 背景:公司,讓部門經(jīng)理,打印此部門的員工姓名

  • 違反迪米特法則的設(shè)計(jì)

    Employee作為局部變量出現(xiàn)在Company中,屬于Company的間接朋友,違反了迪米特

    /*** 公司*/
    public class Company{@Resourceprivate Manager manager;public void printEmployee(String departmentName) {List<Employee> employeeList = manager.getAllEmployeeInfoByDepartmentName(departmentName);for (Employee e : employeeList) {System.out.println(e.getName());}}
    }/*** 部門經(jīng)理*/
    public class Manager{public List<Employee> getAllEmployeeInfoByDepartmentName(String departmentName) {// 內(nèi)部實(shí)現(xiàn):獲取員工信息}
    }/*** 員工*/
    public class Employee{private String name;
    }
    
  • 符合設(shè)計(jì)

    /*** 公司*/
    public class Company{@Resourceprivate Manager manager;public void printEmployee(String departmentName) {manager.printEmployee(departmentName); //Company之和直接直接朋友Manager交流}
    }/*** 部門經(jīng)理*/
    public class Manager{public void getAllEmployeeInfoByDepartmentName(String departmentName) {// 1.獲取員工信息(Manager內(nèi)部實(shí)現(xiàn))List<Employee> employeeList = getAllEmployeeInfoByDepartmentName(departmentName);// 2.打印員工姓名(Manager內(nèi)部實(shí)現(xiàn))printEmployeeName(employeeList);}
    }/*** 員工*/
    public class Manager{private String name;
    }
    

三、23種設(shè)計(jì)模式

3.1創(chuàng)建型:創(chuàng)建對(duì)象

單例、工廠、建造者

3.1.1 單例模式

定義

一個(gè)類只允許創(chuàng)建唯一一個(gè)對(duì)象。這里唯一性作用的范圍是進(jìn)程

最佳實(shí)踐
public class SkuDTO {private SkuDTO(){}private static class SkuDTOHolder {private static final SkuDTO INSTANCE = new SkuDTO();//靜態(tài)內(nèi)部類不會(huì)再外部類被JVM加載到內(nèi)存的時(shí)候一并被加載。什么時(shí)候調(diào)用什么時(shí)候加載,解決了餓漢問(wèn)題//JVM本身保證了SkuDTO只會(huì)在被類加載器加載時(shí)初始化一次,所以是線程安全的}public static SkuDTO getInstance() {return SkuDTOHolder.INSTANCE;}public static void main(String[] args) {for (int i = 0; i < 100; i++) {new Thread(() -> System.out.println(getInstance().hashCode())//都是同一個(gè)對(duì)象).start();}}
}
  • 缺點(diǎn):可以被反射。最完美的方式是枚舉,因?yàn)槊杜e無(wú)構(gòu)造方法,反射也無(wú)法創(chuàng)建新的對(duì)象

  • 優(yōu)點(diǎn):外部類SkuDTO被加載的時(shí)候不會(huì)創(chuàng)建INSTANCE實(shí)例。只要調(diào)用getInstance()方法的時(shí)候才會(huì)去創(chuàng)建實(shí)例。滿足懶加載

    JVM保證了INSTANCE的唯一性、線程安全性

場(chǎng)景
  • 表示全局唯一類

    • 配置類、ID生成器類等
  • 處理共享資源訪問(wèn)沖突(寫日志、共享數(shù)據(jù)庫(kù)連接池等)

    eg

    • 復(fù)現(xiàn):同時(shí)寫日志到txt文件中,可能出現(xiàn)內(nèi)容被覆蓋的情況。原因:多線程并發(fā)寫的時(shí)候,線程1和線程2都創(chuàng)建了FileWriter實(shí)例,獲取到相同的pos待寫入位置,都是從這個(gè)位置寫入,造成內(nèi)容覆蓋
  • 解決:線程1和2使用單例模式創(chuàng)建FileWriter,FileWriter本身是線程安全的,其內(nèi)部實(shí)現(xiàn)了對(duì)象級(jí)別的鎖即相同的FileWriter實(shí)例,在寫操作是線程安全的,不會(huì)被覆蓋。

    public void write(String str, int off, int len) throws IOException {synchronized (lock) {char cbuf[];if (len <= WRITE_BUFFER_SIZE) {if (writeBuffer == null) {writeBuffer = new char[WRITE_BUFFER_SIZE];}cbuf = writeBuffer;} else {    // Don't permanently allocate very large buffers.cbuf = new char[len];}str.getChars(off, (off + len), cbuf, 0);write(cbuf, 0, len);}}
    
線程級(jí)別的單例
public class IDGenerator {private static final AtomicLong id = new AtomicLong(0);private static final ThreadLocal<IDGenerator> tl = new ThreadLocal<>();public IDGenerator getInstance() {tl.set(new IDGenerator());return tl.get();}public Long getId() {return id.incrementAndGet();}
}
  • 同一個(gè)線程獲取到的對(duì)象實(shí)例是相同的,不同線程獲取到的不同。屬于多例
缺點(diǎn)
  • 單側(cè)不友好,全局變量可能會(huì)被修改,造成測(cè)試結(jié)果相互影響問(wèn)題
  • 其他

為了保證全局唯一,除了單例外,我們還可以使用工廠模式來(lái)實(shí)現(xiàn)

3.1.2 工廠模式(簡(jiǎn)單工廠)

場(chǎng)景1:解析配置

將不同后綴的配置文件解析成類

  • 根據(jù)文件路徑x.x.Redis.properties | x.x.MySQL.yaml,創(chuàng)建properties 后綴和yaml后置對(duì)應(yīng)的Parse解析類,解析文件內(nèi)容成對(duì)象

  • 代碼實(shí)現(xiàn)

public class Config{public Config load(String configFilePath) {// 1.獲取配置文件后綴String fileSuffix = getFileSuffix(configFilePath);//(返回properties、yaml、xml等)// 2.根據(jù)后置,創(chuàng)建對(duì)應(yīng)的解析類Configparser parser = createConfigParser(fileSuffix);// 3.解析文件內(nèi)容return parser.parse(fileSuffix);}public Configparser createConfigParser(String fileSuffix) {Configparser parser;if ("xml".equalsIgnoreCase(fileSuffix)) {parser = new XmlConfigparser();} else if ("yaml".equalsIgnoreCase(fileSuffix)) {parser = new YamlConfigparser();} else if ("properties".equalsIgnoreCase(fileSuffix)) {parser = new PropertiesConfigparser();}return parser;}
}
  • 優(yōu)化1:createConfigParser顯然不屬于Config類的內(nèi)容。為了滿足高內(nèi)聚低耦合,需要將createConfigParser方法抽取到獨(dú)立類中。這個(gè)類專門負(fù)責(zé)Configparser的創(chuàng)建,這個(gè)類就是簡(jiǎn)單工廠類
public class ConfigparserFactory{public Configparser createConfigParser(String fileSuffix) {Configparser parser;if ("xml".equalsIgnoreCase(fileSuffix)) {parser = new XmlConfigparser();} else if ("yaml".equalsIgnoreCase(fileSuffix)) {parser = new YamlConfigparser();} else if ("properties".equalsIgnoreCase(fileSuffix)) {parser = new PropertiesConfigparser();}return parser;}
}
  • 優(yōu)化2:

上述代碼每次createConfigParser都會(huì)new一個(gè)新的Configparser對(duì)象。我們可以提前將Configparser對(duì)象創(chuàng)建好放到map中緩存起來(lái),當(dāng)調(diào)用createConfigParser方法時(shí),直接從緩存中拿去。

public class ConfigParserFactory {private static final Map<String, Configparser> map = new HashMap<>();static {map.put("xml", new XmlConfigparser());map.put("yaml", new YamlConfigparser());map.put("properties", new PropertiesConfigparser());}// 這里Configparser是接口,XmlConfigparser是接口實(shí)現(xiàn)類public Configparser createConfigParser(String fileSuffix) {return map.get(fileSuffix);}
}

1、創(chuàng)建名稱特點(diǎn):create、getInstance、newInstance、valueOf、of、as

2、優(yōu)點(diǎn):當(dāng)新增了YmlConfigparser解析類,只需要實(shí)現(xiàn)Configparser接口重寫parse方法即可,然后將其添加到map中。滿足開(kāi)閉原則

場(chǎng)景2:解析表達(dá)式

規(guī)則中心定義卡控最大售賣量規(guī)則,規(guī)則解析

1、規(guī)則枚舉類

@Getter
@AllArgsConstructor
public enum RuleExpEnum{NOT_ALLOW(1,"n","不允許修改"),ALLOW(2,"a","允許修改"),OR_MODEL(3,"o","修改值大于等于補(bǔ)貨算法可修改");public  final int value;public  final String rule;public  final String desc;
}

2、使用場(chǎng)景

  • 前后端交互:前端傳[1,2,3],后端需要解析為[“n”,“a”,“o”]
  • 前后端交互:后端查db數(shù)據(jù)為[“n”,“a”,“o”],需要展示為[“不允許修改”,“允許修改”,“修改值大于等于補(bǔ)貨算法可修改”]

3、簡(jiǎn)單工廠類

@UtilityClass
public class RuleExpFactory {private static final Map<Integer, String> val2RuleMap = new HashMap<>();private static final Map<String, Integer> rule2ValMap = new HashMap<>();private static final Map<String, String> rule2DescMap = new HashMap<>();private static final String AND = "&&";static {for (RuleExpEnum ruleExpEnum : RuleExpEnum.values()) {// 1int value = ruleExpEnum.getValue();// "n"String ruleExp = ruleExpEnum.getRule();// "不允許修改"String desc = ruleExpEnum.getDesc();val2RuleMap.put(value, ruleExp);rule2ValMap.put(ruleExp, value);rule2DescMap.put(ruleExp, desc);}}/*** 1.將n&&o -> "不允許修改且修改值大于等于補(bǔ)貨算法可修改"* 2. 將o  -> "修改值大于等于補(bǔ)貨算法可修改"** @param ruleExp 規(guī)則表達(dá)式"n"* @return        規(guī)則desc"不允許修改"*/public String rule2Desc(String ruleExp) {if (StringUtils.isBlank(ruleExp)) {return StringUtils.EMPTY;}List<String> ruleList = Splitter.on(AND).splitToList(ruleExp);return ruleList.stream().map(rule2DescMap::get).collect(Collectors.joining("且"));}/*** 1.將[1,2] -> "n&&a"* 2. 將[1]  -> "n"** @param valueList  [1,2,3]* @return          "n&&a&&o"*/public String value2Rule(List<Integer> valueList) {if (CollectionUtils.isEmpty(valueList)) {return StringUtils.EMPTY;}return valueList.stream().map(val2RuleMap::get).collect(Collectors.joining(AND));}
}
  • 優(yōu)點(diǎn):

    • 原本實(shí)現(xiàn)是:使用StringBuilder進(jìn)行append
    StringBuilder sb = new StringBuilder();
    if(Objects.equals(rule,"a")) {sb.append("允許修改")
    } else if(Objects.equals(rule,"n")) {sb.append("且");sb,append("不允許修改")
    } else if () {}
    
    • 這種方式缺點(diǎn)很明顯,當(dāng)新增了規(guī)則rule,則需要再新增else if判斷,再添加desc,不滿足開(kāi)閉原則。
    • 如果規(guī)則rule很多,則代碼充斥著大量的else if分支判斷
實(shí)戰(zhàn)-Calendar類

1、創(chuàng)建Calendar實(shí)例

 Calendar instance = Calendar.getInstance();

2、簡(jiǎn)答工廠模式

private static Calendar createCalendar(TimeZone zone,Locale aLocale){//這里zone和aLocale(zh_CN)都是默認(rèn)值Calendar cal = null;//根據(jù)地區(qū)的語(yǔ)言和國(guó)家來(lái)判斷日歷類型if (cal == null) {if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {cal = new BuddhistCalendar(zone, aLocale);} else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"&& aLocale.getCountry() == "JP") {cal = new JapaneseImperialCalendar(zone, aLocale);} else {// 其他情況一律返回公歷cal = new GregorianCalendar(zone, aLocale);}}return cal;}

為什么說(shuō)這是一種簡(jiǎn)單工廠模式呢?

因?yàn)殪o態(tài)createCalendar() 創(chuàng)建實(shí)例時(shí),根據(jù)該方法傳入的參數(shù)來(lái)返回對(duì)應(yīng)的 Calendar 實(shí)現(xiàn)類,符合工廠模式的思想(類似場(chǎng)景1)

總結(jié):

當(dāng)代碼中存在大量if - else,根據(jù)A獲取|創(chuàng)建B的場(chǎng)景,則可以考慮使用簡(jiǎn)單工廠模式

3.1.3DI依賴注入

定義

相當(dāng)于一個(gè)大型工廠,負(fù)責(zé)在程序啟動(dòng)時(shí),根據(jù)各種配置信息,創(chuàng)建對(duì)象。因?yàn)樗钟幸欢褜?duì)象,所以又叫容器

和簡(jiǎn)單工廠區(qū)別
  • 簡(jiǎn)單工廠負(fù)責(zé)一類(eg不同文本類型)對(duì)象的創(chuàng)建。一般要?jiǎng)?chuàng)建哪些對(duì)象,都是代碼提前寫死的new好
  • DI容器負(fù)責(zé)的是整個(gè)應(yīng)用程序所有對(duì)象的創(chuàng)建。除此之外,它還要負(fù)責(zé)對(duì)象生命周期的管理。DI事先不知道要?jiǎng)?chuàng)建哪些對(duì)象,是根據(jù)解析配置來(lái)動(dòng)態(tài)創(chuàng)建對(duì)象
手動(dòng)實(shí)現(xiàn)一個(gè)DI容器
使用
  • Demo
public class Demo {public static void main(String[] args) {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");RateLimiter rateLimiter = (RateLimiter) applicationContext.getBean("rateLimit");rateLimiter.func();}
}
  • 接口
public interface ApplicationContext {Object getBean(String beanId);
}
  • 實(shí)現(xiàn)類
public class ClassPathXmlApplicationContext implements ApplicationContext {private BeanConfigParser beanConfigParser;private BeansFactory beansFactory;public ClassPathXmlApplicationContext(String configLocation) {this.beansFactory = new BeansFactory();this.beanConfigParser = new XmlBeanConfigParser();loadBeanDefinitions(configLocation);}// 解析器,讀取xml配置為BD,并將BD放入beanFactoryprivate void loadBeanDefinitions(String configLocation) {InputStream in = this.getClass().getResourceAsStream("/" + configLocation);List<BeanDefinition> beanDefinitions = beanConfigParser.parse(in);beansFactory.addBeanDefinitions(beanDefinitions);}// 從beanFactory創(chuàng)建bean@Overridepublic Object getBean(String beanId) {return beansFactory.getBean(beanId);}
}
  • DeanDefination
@Data
public class BeanDefinition {private String id;private String className;private List<ConstructorArg> constructorArgs = new ArrayList<>();private Scope scope = Scope.SINGLETON;//單例private boolean lazyInit = false;//懶加載falsepublic boolean isSingleton() {return scope.equals(Scope.SINGLETON);}public static enum Scope {SINGLETON,PROTOTYPE}@Datapublic static class ConstructorArg {private boolean isRef;//bean中是否有對(duì)象依賴private Class type;//對(duì)象依賴類型private Object arg;}
}
1、配置解析
  • xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="rateLimiter" class="com.xxx.lean.RateLimiter"><constructor-arg ref="redis"></constructor-arg></bean><bean id="redis" class="com.mjp.lean.Redis"><constructor-arg type="java.lang.String" value="127.0.0.1"/><constructor-arg type="int" value="6001"/></bean>
</beans>
  • 配置類
@NoArgsConstructor
@AllArgsConstructor
@Data
public class RateLimiter {private Redis redis;
}@AllArgsConstructor
@NoArgsConstructor
@Data
public class Redis {private String ipAddress;private int port;
}
  • 解析配置類并生成BeanDefination放入BeanFactory

主要就是將is解析成BD

public interface BeanConfigParser {List<BeanDefinition> parse(InputStream inputStream);}public class XmlBeanConfigParser implements BeanConfigParser {@Overridepublic List<BeanDefinition> parse(InputStream inputStream) {String content = null;return parse(content);}
}
2、BeanFactory通過(guò)反射創(chuàng)建對(duì)象
public class BeansFactory {private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();private ConcurrentHashMap<String, BeanDefinition> beanDefinitions = new ConcurrentHashMap<>();// 存儲(chǔ)BDpublic void addBeanDefinitions(List<BeanDefinition> beanDefinitionList) {for (BeanDefinition beanDefinition : beanDefinitionList) {this.beanDefinitions.putIfAbsent(beanDefinition.getId(), beanDefinition);}for (BeanDefinition beanDefinition : beanDefinitionList) {if (beanDefinition.isLazyInit() == false && beanDefinition.isSingleton())createBean(beanDefinition);}}// 獲取beanpublic Object getBean(String beanId) {BeanDefinition beanDefinition = beanDefinitions.get(beanId);return createBean(beanDefinition);}// 反射創(chuàng)建beanprotected Object createBean(BeanDefinition beanDefinition) {// 單例則直接從池中拿取對(duì)象并返回if (beanDefinition.isSingleton() && singletonObjects.contains(beanDefinition)) {return singletonObjects.get(beanDefinition.getId());}Object bean = null;try {Class beanClass = Class.forName(beanDefinition.getClassName());List<BeanDefinition.ConstructorArg> args = beanDefinition.getConstructorArgs();// 如果此bean沒(méi)有依賴的bean,則直接創(chuàng)建對(duì)象即可if (args.isEmpty()) {bean = beanClass.newInstance();} else {// 否則,需要按個(gè)創(chuàng)建依賴的BD對(duì)象的beanClass[] argClasses = new Class[args.size()];Object[] argObjects = new Object[args.size()];for (int i = 0; i < args.size(); ++i) {BeanDefinition.ConstructorArg arg = args.get(i);if (arg.isRef()) {// 當(dāng)此bean對(duì)象的構(gòu)造函數(shù)中參數(shù)是ref類型時(shí),則遞歸創(chuàng)建ref屬性指向的對(duì)象BeanDefinition refBeanDefinition = beanDefinitions.get(arg.getArg());argClasses[i] = Class.forName(refBeanDefinition.getClassName());//依賴BD的ref類型(User.class)argObjects[i] = createBean(refBeanDefinition);//依賴BD的具體值User("mjp",18)} else {argClasses[i] = arg.getType();argObjects[i] = arg.getArg();}}// 通過(guò)反射獲取有參構(gòu)造器,然后再通過(guò)newInstance傳遞構(gòu)造器入?yún)⒅?#xff0c;創(chuàng)建對(duì)象bean = beanClass.getConstructor(argClasses).newInstance(argObjects);}} catch (Exception e) {}// 如果對(duì)象時(shí)單例的,則需要放入緩存池中if (bean != null && beanDefinition.isSingleton()) {singletonObjects.putIfAbsent(beanDefinition.getId(), bean);return singletonObjects.get(beanDefinition.getId());}return bean;}
}
3、對(duì)象生命周期管理
  • 單例
  • 懶加載:lazy-init = false。所有對(duì)象在應(yīng)用啟動(dòng)的時(shí)候就創(chuàng)建好
  • init-method 和 destroy-method

比如 initmethod=loadProperties(),在創(chuàng)建好對(duì)象后,會(huì)主動(dòng)調(diào)用 init-method屬性指定的方法來(lái)初始化對(duì)象。

destroy-method=updateConfigFile(),在對(duì)象被最終銷毀之前,會(huì)主動(dòng)調(diào)用 destroy-method 屬性指定的方法來(lái)做一些清理工作(釋放數(shù)據(jù)庫(kù)連接池、關(guān)閉文件)。

3.1.4 Builder建造者模式

和set以及構(gòu)造器的區(qū)別

1、構(gòu)造器的缺點(diǎn)

如果類中有很多的屬性,則new X(太多的屬性,容易賦值錯(cuò))

2、set方法的缺點(diǎn)

  • 即使對(duì)象被final修飾,也是對(duì)象指向的地址是不可變的,但是堆地址的內(nèi)容還是可以通過(guò)set賦值可變。

當(dāng)要求對(duì)象一旦被new其屬性值就不允許被修改,則不能對(duì)外暴露set

  • set方法也無(wú)法校驗(yàn)傳遞參數(shù)是否正確,更無(wú)法校驗(yàn)多個(gè)屬性之間的關(guān)系(eg:最大線程數(shù) 必須 > 核心線程數(shù))

3、建造者的缺點(diǎn):

建造者內(nèi)部類中也需要再定義一遍和外部類中一樣的屬性

建造者模式創(chuàng)建對(duì)象

1、private 構(gòu)造器

2、只提供get方法,不提供set

3、定義成員內(nèi)部類Builder類

  • 單個(gè)setXxx中可以校驗(yàn)?zāi)硞€(gè)屬性
  • 最終build方法new 對(duì)象之前,可以校驗(yàn)各個(gè)屬性之間的關(guān)系
@Getter
@ToString
public class ThreadConfig {private String name;private Integer coreCount;private Integer maxCount;private ThreadConfig(ThreadConfigBuilder threadConfigBuilder) {this.name = threadConfigBuilder.name;this.coreCount = threadConfigBuilder.coreCount;this.maxCount = threadConfigBuilder.maxCount;}@ToStringprivate class ThreadConfigBuilder {public String name;public Integer coreCount;public Integer maxCount;public ThreadConfigBuilder setName(String name) {if (StringUtils.isBlank(name)) {throw new IllegalArgumentException("線程池名稱不能為空");}this.name = name;return this;}public ThreadConfigBuilder setCoreCount(Integer coreCount) {if (coreCount == null || coreCount <= 0) {throw new IllegalArgumentException("線程池核心線程數(shù)必須為正整數(shù)");}this.coreCount = coreCount;return this;}public ThreadConfigBuilder setMaxCount(Integer maxCount) {if (maxCount == null || maxCount <= 0) {throw new IllegalArgumentException("線程池最大線程數(shù)必須為正整數(shù)");}this.maxCount = maxCount;return this;}public ThreadConfig build() {if (coreCount > maxCount) {throw new IllegalArgumentException("線程池最大線程數(shù)必須大于核心線程數(shù)");}return new ThreadConfig(this);}}
}
實(shí)戰(zhàn)@Accessors
@Data
@Accessors(chain = true)
public class UserDemo {private String name;private Integer age;
}UserDemo m = new UserDemo().setName("m").setAge(18);
和工廠模式的區(qū)別

工廠模式是創(chuàng)建一系列相同類型的對(duì)象

建造者模式是創(chuàng)建一個(gè)復(fù)雜屬性的對(duì)象

3.2結(jié)構(gòu)型:類或?qū)ο蟮慕M合

代理、裝飾者、適配器、享元

3.2.1Proxy代理模式

定義

在不改變?cè)蓄惖那闆r下,引入代理類來(lái)給原始類附加功能

場(chǎng)景

日志打印、權(quán)限校驗(yàn)、限流、事務(wù)、最大努力重試

動(dòng)態(tài)代理接口實(shí)現(xiàn)

為給個(gè)接口方法的執(zhí)行,計(jì)算花費(fèi)的時(shí)間

public class StopWatchProxy {public Object creatProxy(Object target) {Class<?> aClass = target.getClass();ClassLoader classLoader = aClass.getClassLoader();Class<?>[] interfaces = aClass.getInterfaces();return Proxy.newProxyInstance(classLoader, interfaces, (proxy, method, args) -> {long start = System.currentTimeMillis();Object result = method.invoke(target, args);long end = System.currentTimeMillis();System.out.println((end - start));return result;});}
}
public interface User {void eat();
}public class UserImpl implements User{@Overridepublic void eat() {System.out.println("eat");}
}
StopWatchProxy stopWatchProxy = new StopWatchProxy();
User user = (User) stopWatchProxy.creatProxy(new UserImpl());
user.eat();

3.2.2 裝飾器模式

作用

給原始類添加增強(qiáng)功能

和代理模式的區(qū)別

代理模式中代理類附加的是跟原始類無(wú)關(guān)的功能(日志、權(quán)限校驗(yàn)等);裝飾器類附加的是跟原始類相關(guān)的增強(qiáng)功能(原始類是直接讀、裝飾類增加的功能是緩存讀)

實(shí)現(xiàn)
  • 裝飾器類(ADeractor)需要跟原始類(A)繼承相同的抽象類(AbstractA) | 接口(IA)。
  • 裝飾器類(ADeractor)中組合原始類(A)

可以對(duì)A嵌套使用多個(gè)裝飾器類

  • 接口|抽象類
public interface IA {void f();
}
  • 原始類
@Service
public class A implements IA{@Overridepublic void f() {System.out.println("f");}
}
  • 裝飾類
@Service
public class ADecorator implements IA{@Resourceprivate A a;@Overridepublic void f() {// 增強(qiáng)System.out.println("增強(qiáng)1");a.f();// 增強(qiáng)System.out.println("增強(qiáng)2");}
}
  • 使用
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ApplicationLoader.class)
public class SpringTest {@Resourceprivate ADecorator aDecorator;@Testpublic void test() {aDecorator.f();}
}
場(chǎng)景IO流
1、結(jié)構(gòu)
  • 字節(jié)流-讀InpuStream
    • FileInputStream
    • ByteArrayInputStream
    • FilterInputStream
      • BufferedInputStream
      • DateInputStream
  • 字符流-讀Reader
    • BufferedReader
    • InputStreamReader
      • FileReader
2、源碼結(jié)構(gòu)

抽象類:InputStream

A:FileInputStream

ADecorator:BufferedInputStream、DateInputStream

FileInputStream fis = new FileInputStream(new File("xxx.txt"));
BufferedInputStream bis = new BufferedInputStream(fis);
bis.read();
3、前置背景

3.1 使用裝飾者增強(qiáng)后的read

    @Testpublic void test() throws IOException {FileInputStream fis = new FileInputStream(new File(""));SonBufferedInputStream bis = new SonBufferedInputStream(fis);bis.read();}

3.3 抽象-read

public abstract class InputStream implements Closeable {public abstract int read() throws IOException;
}

3.3 A-read

public class FileInputStream extends InputStream{public int read() throws IOException {return read0();}  private native int read0() throws IOException;
}

3.4 ADecorator -read

public class SonBufferedInputStream extends FatherFilterInputStream {public SonBufferedInputStream(FileInputStream fis) {super(fis);}public int read() throws IOException {// A的readfis.read(null, 1, 1);// 增強(qiáng)return 1;}
}
  • 雖然ADecorator(SonBufferedInputStream)中沒(méi)有直接定義屬性A(FileInputStrteam),但是其父類中定義了
@Data
@AllArgsConstructor
public class FatherFilterInputStream extends InputStream {protected FileInputStream fis;@Overridepublic int read() throws IOException {return fis.read();}
}

這樣當(dāng)執(zhí)行SonBufferedInputStream bis = new SonBufferedInputStream(fis)時(shí)

  • super(fis)
  • 即將A(FileInputStrteam)賦值給了其父類屬性A a,這樣等效子類ADecorator也具有了A a屬性值
  • 所以,當(dāng)執(zhí)行bis.read()時(shí),一方面執(zhí)行了a.read,另一方面執(zhí)行了增強(qiáng)方法。實(shí)現(xiàn)了裝飾功能
4、源碼解析

4.1 抽象類InputStream-read()

4.2 A : read(是個(gè)nativate方法)

public class FileInputStream extends InputStream{private native int read() throws IOException;
}

4.3 ADecorator(BufferedInputStream)-read

這里的BufferedInputStream bis = new BufferedInputStream(fis);
=public BufferedInputStream(InputStream in, int size) {super(in);//super(fis)buf = new byte[size];}
=>
public class FilterInputStream extends InputStream {//即ADecorator中組合了A(fis)protected volatile InputStream in;//fisprotected FilterInputStream(InputStream in) {this.in = in;//this.fis = fis}   
}

==》等效

public class FilterInputStream extends InputStream {protected volatile FileInputStream fis;protected FilterInputStream(FisleInputStream fis) {this.fis = fis;}   
}

這樣一來(lái),就相當(dāng)于子類ADecorator(Buffered)中通過(guò)繼承父類也具有屬性A(FileInputStream)

4.4 bis.read()

public synchronized int read() throws IOException {fill();//實(shí)現(xiàn)return getBufIfOpen()[pos++] & 0xff;
}
private void fill() throws IOException {// 增強(qiáng)功能緩存byte[] buffer = getBufIfOpen();// 調(diào)用屬性a 的方法int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
}
private InputStream getInIfOpen() throws IOException {InputStream input = in;//fisreturn input;
}

getInIfOpen().read(buffer, pos, buffer.length - pos) ==相當(dāng)于使用fis.read(buffer, pos, buffer.length - pos)

public class FileInputStream{public int read(byte b[], int off, int len) throws IOException {return readBytes(b, off, len);}private native int readBytes(byte b[], int off, int len) throws IOException;
}

這樣一來(lái),ADcorator中使用了buffer增強(qiáng)了fis的read

4.5 父類作用

完全可以直接將屬性A a放入ADecorator中

public class BufferedInputStream extends InputStream {private FileInputStream fis;public int read(){//}
}

為什么采用,將A a 放入父類中,然后子類繼承父類屬性的方式從而子類ADecorator也具有了屬性A。

父類作用:

  • 讓自子類Buffered、Data只需要關(guān)注A(Fis)中需要增強(qiáng)的方法,比如A中的read方法的Buffered為其增強(qiáng)為具有緩存功能的字節(jié)流讀。

  • A類的其它不需要增強(qiáng)的方法都交給父類FilterInputStream的去關(guān)注去實(shí)現(xiàn),這樣眾多裝飾者子類就無(wú)需重寫

3.2.3 適配器模式

定義

將不兼容的接口轉(zhuǎn)為可兼容的接口,讓原本因?yàn)榻涌诓患嫒轃o(wú)法在一起工作的類可以一起工作

實(shí)現(xiàn)
1、類適配器-繼承
  • 需要配適配的
public class Adaptee {public void query() {System.out.println("query");}public void add() {System.out.println("add");}public void delete() {System.out.println("delete");}
}
  • 適配成什么樣子,即目標(biāo)

    除了add方法還是使用Adaptee的,查詢和刪除都使用適配后的新方法

public interface Target {void queryNew();void add();void deleteNew();
}
  • 適配器
@Service
public class Adapter extends Adaptee implements Target{@Overridepublic void queryNew() {super.query();}@Overridepublic void deleteNew() {if (true) {// 執(zhí)行新的刪除邏輯System.out.println("delete new");} else {super.delete();}}// 這里類適配器最大的特點(diǎn)就是:理論上需要重寫add方法,但是由于繼承了父類的add,所以可以不用重寫add
}
  • 使用
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ApplicationLoader.class)
@Slf4j
public class SpringTest {@Resourceprivate Target target;@Testpublic void test() throws IOException {target.queryNew();target.add();target.deleteNew();}
}
query
add
delete new
  • 使用場(chǎng)景

目標(biāo)Target和原Adaptee中大部分方法都一樣,沒(méi)有那么多方法需要適配的時(shí)候,使用類繼承,這樣很多方法(比如像add)都無(wú)需重寫。

2、對(duì)象適配器-組合
  • 適配器
@Service
public class AdapterObj implements Target{@Resourceprivate Adaptee adaptee;@Overridepublic void queryNew() {adaptee.query();// 再---}@Overridepublic void add() {adaptee.add();}@Overridepublic void deleteNew() {if (true) {System.out.println("delete new");} else {adaptee.delete();}}
}
  • 使用場(chǎng)景

目標(biāo)Target和原Adaptee中大部分方法都不一樣即定義不同

場(chǎng)景
1、兼容老接口
  • 背景

原本查詢黑名單接口BlackListService#queryBlackList(Long poiId)根據(jù)倉(cāng)id查詢倉(cāng)下的所有很名單。

本次需求查詢黑名單時(shí),除了需要倉(cāng)id外,還需要businessType

  • 實(shí)現(xiàn)方式1

    直接修改BlackListService#queryBlackList(Long poiId,Integer businessType)方法聲明和邏輯

    • 優(yōu)點(diǎn):無(wú)需重構(gòu)

    • 缺點(diǎn):風(fēng)險(xiǎn)大,線上很多業(yè)務(wù)使用到這個(gè)方法,一旦方法有問(wèn)題相當(dāng)于全量了,風(fēng)險(xiǎn)不可控

  • 實(shí)現(xiàn)方式2:適配器模式

    @Service
    public class BlackListAdaptee {public List<Object> queryBlackList(Long poiId){return Lists.newArrayList();}
    }
    
    public interface BlackListServiceTarget {List<Object> queryBlackListNew(Long poiId, Integer businessType);
    }
    
    @Service
    public class BlackListServiceAdaptor extends BlackListAdaptee implements BlackListServiceTarget{@Overridepublic List<Object> queryBlackListNew(Long poiId, Integer businessType) {if (true) {//命中了灰度倉(cāng)System.out.println("根據(jù)poi和businessType查詢黑名單");return Lists.newArrayList();} else {// 非灰度倉(cāng)走老邏輯查詢,查詢結(jié)果按照businessType進(jìn)行過(guò)濾即可List<Object> blackList = super.queryBlackList(poiId);return blackList.stream().filter(Objects::nonNull).collect(Collectors.toList());}}
    }
    
    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = ApplicationLoader.class)
    @Slf4j
    public class SpringTest {@Resourceprivate BlackListServiceAdaptor blackListServiceAdaptor;@Resourceprivate BlackListServiceAdaptee blackListServiceAdaptee;@Testpublic void test() throws IOException {// 原本的業(yè)務(wù)邏輯//List<Object> result = blackListServiceAdaptee.queryBlackList(323L);// 改為:List<Object> result = blackListServiceAdaptor.queryBlackListNew(323L, 1);}
    }
    
    • 優(yōu)點(diǎn):風(fēng)險(xiǎn)可控,一旦方法有問(wèn)題直接切灰度即可。
2、統(tǒng)一多個(gè)類的接口設(shè)計(jì)
  • 背景

敏感詞過(guò)濾。

  • 實(shí)現(xiàn)方式

引入第三方過(guò)濾Jar(A:性關(guān)鍵詞相關(guān)、B:政治關(guān)鍵字相關(guān))

public class RishManagement{@Resourceprivate A a;@Resourceprivate B b;public String filterSensitiveWords(String text) {String s = a.filterSexyWords(text);//內(nèi)部默認(rèn)實(shí)現(xiàn)使用xxx代替敏感詞return b.filterPoliticalWords(s,"???");//使用???代替敏感詞}
}
  • 問(wèn)題

當(dāng)需要環(huán)境污染過(guò)濾C時(shí),這個(gè)時(shí)候RishManagement會(huì)違背開(kāi)閉原則

A依賴提供的方法-單個(gè)入?yún)?#xff1a;使用默認(rèn)實(shí)現(xiàn)使用xxx代替敏感詞

B依賴提供的方法-兩個(gè)入?yún)?#xff0c;第二個(gè)入?yún)⑹莚eplace:使用String replace 代替敏感詞

這樣接口的調(diào)用也不統(tǒng)一,還需要人為指定replace

  • 解決:使用對(duì)象適配器模式統(tǒng)一接口設(shè)計(jì)

    • Adaptee:AFilterAdaptee、BFilterAdaptee都是第三方依賴
    • Target:制定統(tǒng)一的接口設(shè)計(jì),否是單入?yún)?/li>
    public interface SensitiveWordsFilterTarget {String filter(String text);
    }
    
    • A-Adaptor
    @Service
    public class SexyWordsFilterAdapter implements SensitiveWordsFilterTarget{@Resourceprivate AFilterAdaptee aFilterAdaptee;@Overridepublic String filter(String text) {return aFilterAdaptee.filterSexyWords(text);}
    }
    
    • B-Adaptor
    @Service
    public class PoliticalWordsFilterAdapter implements SensitiveWordsFilterTarget{@Resourceprivate BFilterAdaptee bFilterAdaptee;@Overridepublic String filter(String text) {return bFilterAdaptee.filterPoliticalWords(text, "???");}
    }
    
    • RiskManager
    @Service
    public class RiskManager {@Resourceprivate List<SensitiveWordsFilterTarget> sensitiveWordsFilterTargets;public String filterWords(String text) {String temp = text;for (SensitiveWordsFilterTarget filterAdaptor : sensitiveWordsFilterTargets) {temp = filterAdaptor.filter(temp);}return temp;}
    }
    

    這樣當(dāng)需要過(guò)濾:環(huán)境污染相關(guān)關(guān)鍵詞引入C時(shí),不需要修改RiskManager,只需要?jiǎng)?chuàng)建C-EnvironmentWordsFilterAdapter即可

3.2.4 享元模式

定義

被共享的單元(比如類的屬性,當(dāng)這些屬性是通用且不可變時(shí),可以組成元,讓系統(tǒng)共享使用)

作用

系統(tǒng)復(fù)用不可變隊(duì)形-享元,節(jié)省內(nèi)存

實(shí)戰(zhàn)
  • 背景

    建設(shè)一個(gè)象棋棋牌室游戲,同時(shí)在線1w個(gè)房間,每個(gè)房間是一盤對(duì)局(棋局類),對(duì)局中需要棋子(棋子類)

    • 棋子
    @Data
    @AllArgsConstructor
    public class ChessPiece {/*** 棋子編號(hào)1-32(紅黑各16)*/private Integer id;private Color color;/*** 將、士、車、馬---*/private String name;/*** 棋子在棋局上的位置*/private Integer x;private Integer y;public enum Color{RED,BLACk;}
    }
    
    • 棋局
    public class ChessBoard {private Map<Integer, ChessPiece> pieceIdMap = new HashMap<>();public ChessBoard() {init();}private void init() {pieceIdMap.put(1, new ChessPiece(1, ChessPiece.Color.RED, "車", 0 , 1));pieceIdMap.put(2, new ChessPiece(2, ChessPiece.Color.BLACk, "跑", 7 , 4));// 剩下30個(gè)棋子}
    }
    
  • 問(wèn)題

如果游戲有1w個(gè)房間,則有1w個(gè)棋局,再創(chuàng)建每個(gè)棋局的時(shí)候,都需要?jiǎng)?chuàng)建32個(gè)棋子對(duì)象。所以需要?jiǎng)?chuàng)建32w個(gè)棋子對(duì)象。占用很大內(nèi)存。

  • 解決

我們發(fā)現(xiàn)棋子屬性只有x、y坐標(biāo)屬性,對(duì)于不同房間的棋局棋子的坐標(biāo)是不同的

id、顏色、名稱,對(duì)于不同棋局來(lái)說(shuō)都是相同的屬性,這些屬性都是不可變的可以共享,可以抽取為享元類

享元類

@Data
@AllArgsConstructor
public class ChessPieceUnit {/*** 棋子編號(hào)1-32(紅黑各16)*/private Integer id;private ChessPiece.Color color;/*** 將、士、車、馬---*/private String name;public enum Color{RED,BLACk;}
}

棋子類

@Data
@AllArgsConstructor
public class ChessPiece {/*** 享元類(id、color、name)*/private ChessPieceUnit chessPieceUnit;/*** 棋子在棋局上的位置*/private Integer x;private Integer y;
}

享元工廠類:存取享元類

public class ChessPieceUnitFactory {private static final Map<Integer, ChessPieceUnit> pieceIdMap = new HashMap<>();static {pieceIdMap.put(1, new ChessPieceUnit(1, ChessPieceUnit.Color.RED, "車"));pieceIdMap.put(2, new ChessPieceUnit(2, ChessPieceUnit.Color.BLACk, "跑"));// 剩下30個(gè)棋子}private ChessPieceUnit getUnitByChessPieceId(Integer id) {return pieceIdMap.get(id);}
}

棋局類

public class ChessBoard {private static final Map<Integer, ChessPiece> pieceIdAndPieceMap = new HashMap<>();public ChessBoard() {init();}private void init() {pieceIdAndPieceMap.put(1, new ChessPiece(ChessPieceUnitFactory.pieceIdMap.get(1), 0 , 1));pieceIdAndPieceMap.put(2, new ChessPiece(ChessPieceUnitFactory.pieceIdMap.get(2), 0 , 1));// 剩下30個(gè)棋子}
}

棋局類在put棋子的時(shí)候,棋子的享元部分屬性是通過(guò)享元工廠獲取的。

  • 棋局A:在put棋子的時(shí)候,享元類屬性是通過(guò)享元類工廠緩存map獲取的,map只需要第一次創(chuàng)建時(shí)new一個(gè)享元類

  • 棋局B:后續(xù)棋局B使用享元類時(shí),直接存緩存map獲取即可,無(wú)需再創(chuàng)建。

  • 即1w棋局,需要?jiǎng)?chuàng)建32w個(gè)棋子類,但是棋子類中大量的屬性即享元屬性只需要?jiǎng)?chuàng)建1次,這樣無(wú)疑大大減少內(nèi)存的占用。(1w房間,1w棋局類、32w棋子類、32個(gè)享元屬性)

場(chǎng)景
Integer實(shí)戰(zhàn)

1、背景

Integer i1 = new Integer(123);
Integer i2 = 123;//等價(jià)Integer i3 = Integer.valueOf(123);

2、原理

    public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)return IntegerCache.cache[i + (-IntegerCache.low)];return new Integer(i);}
  • low:-128

  • high:127

  • 當(dāng)i的值在 -128-127之間,則從IntegerCache.cache緩存數(shù)組中獲取,這個(gè)數(shù)據(jù)大小是256

    [-128,-127,—0,1,—,127]

    不在這個(gè)區(qū)間,則new一個(gè)新的對(duì)象

  • Integer.valueOf(1)即cache[1-(-128)] = cache[129] = 1

  • new Integer(123),不使用IntegerCache.cache緩存,直接創(chuàng)建對(duì)象

3、作用

若需要?jiǎng)?chuàng)建1w個(gè)-128-127的數(shù)字。方式1:new1w個(gè)對(duì)象

方式2|3則只需要new 256個(gè)對(duì)象

4、其他

String字符串常量池同理

總結(jié)

將共享的,再創(chuàng)建完成后,使用緩存(數(shù)組、map)存儲(chǔ)起來(lái)。后續(xù)直接從緩存中取

3.3行為型

觀察者、模板、策略、職責(zé)鏈、迭代器、狀態(tài)模式

3.3.1 觀察者模式

定義

對(duì)象之間定義一個(gè)1 vs n的依賴,當(dāng)1的狀態(tài)改變時(shí),所有依賴于它的n對(duì)象,都會(huì)接收到通知并更新

被觀察者:subject

觀察者: observe

作用

將被觀察者和觀察者解耦

和生產(chǎn)者消費(fèi)者的區(qū)別

1、生產(chǎn)消費(fèi)模式:

  • 生產(chǎn)和消費(fèi)是不同的線程。二者通過(guò)隊(duì)列通信
  • 多對(duì)多(生產(chǎn)者可以多個(gè):雜志的投稿者,消費(fèi)者也可以多個(gè))
  • 為了解耦 和 并發(fā)
  • 生產(chǎn)者推、消費(fèi)者拉

2、觀察者模式

  • 被觀察者和觀察者,在同一個(gè)線程中
  • 1對(duì)多
  • 為了解耦被觀察者和觀察者
  • 被觀察者內(nèi)部組合了觀察者,需要維護(hù)觀察者的信息,屬于被觀察者直接將信息推送給觀察者們
和 訂閱-發(fā)布模式的區(qū)別

1、訂閱-發(fā)布模式

  • 是觀察者的別名,但是后續(xù)演變成一種新的設(shè)計(jì)模式
  • 發(fā)布者,不再維護(hù)訂閱者們的信息,不會(huì)再直接將信息推送個(gè)發(fā)布者們,實(shí)現(xiàn)了二者的完全解耦
  • 發(fā)布者和訂閱者之間,存在中間件:調(diào)度中心Broker
    • 發(fā)布者只需要告訴Broker,我要發(fā)送的信息,topic為A
    • 訂閱者只需要告訴Broker,我訂閱的消息,topic為A
    • 當(dāng)Broker接收到Topic為A的消息時(shí),會(huì)統(tǒng)一調(diào)度那些訂閱了Topic為A的訂閱者們注冊(cè)到Broker的處理代碼
    • eg:你在微博上關(guān)注了A,其他人也關(guān)注了A。當(dāng)A發(fā)布動(dòng)態(tài),即發(fā)送消息到微博調(diào)度中心Broker時(shí),Broker就會(huì)為你們推送A的動(dòng)態(tài)
被觀察者-觀察者模板

被觀察者需要維護(hù)觀察者們的信息

1、抽象觀察者

public interface Observe {void update();
}

2、具體觀察者

@Service
public class Observe1 implements Observe{@Overridepublic void update() {System.out.println("觀察者1更新");}
}@Service
public class Observe2 implements Observe{@Overridepublic void update() {System.out.println("觀察者2更新");}
}

3、被觀察

@Service
public class Subject {@Resourceprivate List<Observe> observes;public void notice() {for (Observe observe : observes) {observe.update();}}
}

使用

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ApplicationLoader.class)
@Slf4j
public class SpringTest {@Resourceprivate Subject subject;@Testpublic void test(){subject.notice();}
}
觀察者1更新
觀察者2更新
實(shí)戰(zhàn)

1、背景

用戶注冊(cè)app成功后,給用戶發(fā)放新人券

2、實(shí)現(xiàn)

@RestController
public class UserAppController {@Resourceprivate RegisterService registerService;@Resourceprivate RegisterSuccessObserve registerSuccessObserve;public void register(Long iphone, String pwd) {// 注冊(cè)boolean success = registerService.register(iphone, pwd);if (success) {registerSuccessObserve.issueNewConsumerCoupon(iphone);}}
}
  • 注冊(cè)
@Service
public class RegisterService {public boolean register(Long iphone, String pwd) {return true;}
}
  • 發(fā)送新人優(yōu)惠券
@Service
public class RegisterSuccessObserve {public void issueNewConsumerCoupon(Long iphone){System.out.println("發(fā)送新人優(yōu)惠券");}
}

3、問(wèn)題

如后續(xù)新需求:當(dāng)用戶注冊(cè)App成功后,除了發(fā)新人券還需要發(fā)送用戶注冊(cè)成功信息的短信給用戶

那么register方法就必須改動(dòng)了,違背了開(kāi)閉原則

4、使用模式重構(gòu)

被觀察者維護(hù)觀察者們的信息,一旦注冊(cè)成功后,將后續(xù)一系列動(dòng)作推送給觀察者們即可

  • 抽象觀察者
public interface RegisterSSuccessObserver {void update(Long iphone);
}
  • 觀察者們
@Service
public class IssueNewConsumerCouponObserve implements RegisterSSuccessObserver{@Overridepublic void update(Long iphone) {System.out.println("發(fā)送新人優(yōu)惠券");}
}
@Service
public class SendMsgObserve implements RegisterSSuccessObserver{@Overridepublic void update(Long iphone) {System.out.println("發(fā)送注冊(cè)成功短信");}
}
  • 被觀察者
@RestController
public class UserAppControllerSubject {@Resourceprivate RegisterService registerService;@Resourceprivate List<RegisterSSuccessObserver> registerSSuccessObservers;public void register(Long iphone, String pwd) {// 注冊(cè)boolean success = registerService.register(iphone, pwd);if (success) {for (RegisterSSuccessObserver observer : registerSSuccessObservers) {observer.update(iphone);}}}
}

這樣后續(xù),發(fā)送新人券改為發(fā)送禮品卡,也只需要新增具體觀察者即可。對(duì)于register方法,滿足開(kāi)閉原則

5、優(yōu)化- 異步非阻塞觀察者模式

  • 使用guava的EventBus模式實(shí)現(xiàn)異步阻塞

  • 被觀察者

@Slf4j
@RestController
public class UserAppControllerSubject {@Resourceprivate RegisterService registerService;@Resourceprivate List<RegisterSSuccessObserver> registerSSuccessObservers;private ExecutorService threadPool = Executors.newFixedThreadPool(2);private EventBus eventBus;public UserAppControllerSubject() {eventBus = new AsyncEventBus(threadPool, (e, context) -> {log.error("consumer{}, receive{},msg{} 流程異常", context.getSubscriber(), context.getSubscriberMethod() ,context.getEvent(), e);});}public void register(Long iphone, String pwd) {// 注冊(cè)成功boolean success = registerService.register(iphone, pwd);if (success) {for (RegisterSSuccessObserver observer : registerSSuccessObservers) {// 訂閱者-類似消費(fèi)組eventBus.register(observer);}// 發(fā)布者send發(fā)布-類似MQ生產(chǎn)者的sendeventBus.post(iphone);}}
}
  • 抽象觀察者
public interface RegisterSSuccessObserver {void receive(Long iphone);
}
  • 觀察者們
@Service
public class SendMsgObserve implements RegisterSSuccessObserver{@Override@Subscribepublic void receive(Long iphone) {System.out.println("發(fā)送注冊(cè)成功短信");}
}
@Service
public class IssueNewConsumerCouponObserve implements RegisterSSuccessObserver{@Override@Subscribepublic void receive(Long iphone) {System.out.println("發(fā)送新人優(yōu)惠券");}
}
  • 使用
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ApplicationLoader.class)
@Slf4j
public class SpringTest {@Resourceprivate UserAppControllerSubject userAppControllerSubject;@Testpublic void test(){userAppControllerSubject.register(186L, "123mjp");}
}
發(fā)送注冊(cè)成功短信
發(fā)送新人優(yōu)惠券
  • 優(yōu)化為非阻塞

如果IssueNewConsumerCouponObserve觀察者的receive方法內(nèi)部流程較長(zhǎng),執(zhí)行的慢。可能會(huì)影響整體的性能,則需要非阻塞模式,即其receive方法異步執(zhí)行

@Service
public class IssueNewConsumerCouponObserve implements RegisterSSuccessObserver{private ExecutorService myThreadPool = Executors.newFixedThreadPool(2);@Override@Subscribe@Async("myThreadPool")public void receive(Long iphone) {try {TimeUnit.SECONDS.sleep(5L);} catch (Exception e) {}System.out.println("發(fā)送新人優(yōu)惠券");}
}

啟動(dòng)類@EnableAsync

@EnableAsync
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class ApplicationLoader {public static void main(String[] args) {SpringApplication springApplication = new SpringApplication(ApplicationLoader.class);springApplication.run(args);}
}

3.3.2 模板模式

定義

在抽象類中定義一個(gè)邏輯框架由a、b、c等組成。子類在不改變整體框架的情況下,重新定義業(yè)務(wù)的某些步驟

作用

擴(kuò)展、復(fù)用

使用模板
  • 父類模板
public abstract class Template {public void func() {m1();m2();m3();}protected abstract void m1();protected abstract void m2();private void m3() {}
}
  • 子類重寫模板中某一個(gè)步驟
public class A extends Template{@Overrideprotected void m1() {}@Overrideprotected void m2() {}
}
B類同理
場(chǎng)景
1、復(fù)用

1.1 io

  • 父類框架
public abstract class InputStream implements Closeable {// 父類定義一個(gè)方法框架public int read(byte b[], int off, int len) throws IOException {// m1();read();// m3();}// 其中m2()即read方法,是抽象方法,由不同子類自己去實(shí)現(xiàn)public abstract int read() throws IOException;
}
  • 子類重寫框架中某個(gè)步驟

eg:ByteArrayInputStream會(huì)重寫read方法

1.2 AbstractList

  • 父類框架
    public boolean addAll(int index, Collection<? extends E> c) {rangeCheckForAdd(index);boolean modified = false;for (E e : c) {add(index++, e);modified = true;}return modified;}其中add方法等效abstract類型方法,原因如下public void add(int index, E element) {throw new UnsupportedOperationException();}AbstractList中的add()直接拋出異常,即如果其子類不重寫add方法,那么就會(huì)調(diào)用父類AbstractList的add,直接拋異常。這就和abstract關(guān)鍵字一樣,強(qiáng)制要求子類重寫add方法
2、擴(kuò)展框架

2.1 背景

  • Web項(xiàng)目中的SpringMvc中的XxxController#xxxFunc。請(qǐng)求url對(duì)應(yīng)類上注解 + 方法上注解,流量就能打到

  • 這是SpringMvc封裝了Servlet實(shí)現(xiàn)。自己也可以通過(guò)Servlet的擴(kuò)展功能實(shí)現(xiàn)上述功能

2.2 擴(kuò)展點(diǎn)

Servlet通過(guò)模板模式,留下了doGet、doPost等擴(kuò)展點(diǎn)。讓用戶再不修改框架的情況下,通過(guò)繼承HttpServlet重寫擴(kuò)展點(diǎn)方法,將用戶自己的業(yè)務(wù)代碼嵌入整個(gè)框架中

2.3 源碼解析

  • 自定義XxxServlet#xxxFunc
  • 在web.xml中定義url和Servlet的映射關(guān)系
  • 當(dāng)請(qǐng)求打進(jìn)來(lái)時(shí),會(huì)首先走到HttpServlet的service方法
protected void service(HttpServletRequest req, HttpServletResponse resp){if (method.equals(METHOD_GET)) {if (lastModified == -1) {// 擴(kuò)展點(diǎn)1:doGetdoGet(req, resp);} else {//}} else if (method.equals(METHOD_POST)) {// 擴(kuò)展點(diǎn)2:doPostdoPost(req, resp);} else if (method.equals(METHOD_PUT)) {//}}// doGet等效abstract doGet// 因?yàn)閐oGet方法什么都沒(méi)實(shí)現(xiàn),就是報(bào)錯(cuò)。所以HttpServlet的子類就必須重寫doGet,這一點(diǎn)類似AbstractList#addAll中的add方法protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException{String protocol = req.getProtocol();String msg = lStrings.getString("http.method_get_not_supported");if (protocol.endsWith("1.1")) {resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);} else {resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);}}
  • 子類重寫模板,利用框架的模板模式,實(shí)現(xiàn)擴(kuò)展功能
public class MyHttpServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 自定義}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{// 自定義}
}
3、實(shí)戰(zhàn)

1、背景

父類定義框架方法:根據(jù)圖形周長(zhǎng)、面積,計(jì)算周長(zhǎng) / 面積值

2、實(shí)現(xiàn)

func(){// 計(jì)算周長(zhǎng)double perimerter = perimerter();// 計(jì)算面積double area = area();m3(perimerter, area);
}

3.3.3回調(diào)函數(shù)

定義

A類中的a調(diào)用B類b方法時(shí),B類的b方法會(huì)反過(guò)來(lái)調(diào)用A類中注冊(cè)給它的f方法

作用

復(fù)用

由于和模板方法的復(fù)用功能一樣,所以很多回調(diào)方式直接叫XxxTemplate

場(chǎng)景1
1、背景JDBCTemplate的演變
  • 普通版JDBCDemo

    • 實(shí)現(xiàn)
    public class JDBCDemo {public List<User> queryUserById(Long id) {Connection con = null;Statement stm = null;List<User> ans = null;try {// 1.注冊(cè)驅(qū)動(dòng)Class.forName("com.mysql.jdbc.Driver");// 2.獲取數(shù)據(jù)庫(kù)連接對(duì)象con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/day21", "root", "root");// 3.獲取sql語(yǔ)句的執(zhí)行對(duì)象String sql = "select * from table_user where id = " + id;stm = con.prepareStatement(sql);// 4.執(zhí)行sqlResultSet result = stm.executeQuery(sql);// 5.處理查詢結(jié)果while (result.next()) {User user = new User().setName(result.getString("name"));ans.add(user);}return ans;} catch (Exception e) {} finally {// 6.關(guān)閉資源if (stm != null) {try {stm.close();} catch (SQLException e) {throw new RuntimeException(e);}}if (con != null) {try {con.close();} catch (SQLException e) {throw new RuntimeException(e);}}}return ans;}
    }
    
    • 問(wèn)題

    如果想執(zhí)行update語(yǔ)句,則需要定義updateById(Long id)則整個(gè)流程還需要再走一遍。毫無(wú)復(fù)用性而言

  • 抽取版JDBCUtils

    • jdbc.properties
    driver = com.mysql.jdbc.Driverurl = "jdbc:mysql://127.0.0.1:3306/day21"user = rootpassword = root
    
    • Util
    public class JDBCUtil {private static String driver;private static String url;private static String user;private static String password;/*** 注冊(cè)驅(qū)動(dòng) + 獲取數(shù)據(jù)庫(kù)連接對(duì)象con的前置配置*/static {ClassLoader classLoader = JDBCUtil.class.getClassLoader();InputStream is = classLoader.getResourceAsStream("D:\\CodeBetter\\src\\main\\resources\\jdbc.properties");Properties properties = new Properties();try {properties.load(is);driver = properties.getProperty("driver");Class.forName(driver);url = properties.getProperty("url");user = properties.getProperty("user");password = properties.getProperty("password");} catch (Exception e) {}}/*** 獲取數(shù)據(jù)庫(kù)連接對(duì)象* * @return* @throws SQLException*/public static Connection getConnection() throws SQLException {return DriverManager.getConnection(url, user, password);}/*** 關(guān)閉資源* * @param con* @param stm*/public static void close(Connection con , Statement stm){if (stm != null) {try {stm.close();} catch (SQLException e) {throw new RuntimeException(e);}}if (con != null) {try {con.close();} catch (SQLException e) {throw new RuntimeException(e);}}}
    }
    
    • 使用
            Connection con = JDBCUtil.getConnection();PreparedStatement pst;// 1.查詢String sql = "select * from table_user where id = " + 1;pst = con.prepareStatement(sql);ResultSet resultSet = pst.executeQuery();// 2.更新String updateSql = "update from table_user set name = mjp where id = 1";pst = con.prepareStatement(updateSql);int result = pst.executeUpdate();// 3.關(guān)閉JDBCUtil.close(con, pst);
    
    • 問(wèn)題:抽象的仍不徹底。仍有大量con、stm相關(guān)對(duì)象
  • JDBCTemplate版本

public class DataSourceDemo {private static DataSource ds;private static Properties properties;static {ClassLoader classLoader = DataSourceDemo.class.getClassLoader();InputStream is = classLoader.getResourceAsStream("D:\\CodeBetter\\src\\main\\resources\\jdbc.properties");properties = new Properties();try {// 這里會(huì)把properties中所有屬性都讀取到properties.load(is);} catch (Exception e) {}}public static DataSource getDataSource() {try {ds = DruidDataSourceFactory.createDataSource(properties);} catch (Exception e) {throw new RuntimeException(e);}return ds;}
}
        JdbcTemplate jtl = new JdbcTemplate(DataSourceDemo.getDataSource());// 1.查詢String sql = "select * from table_user where id = " + 1;Map<String, Object> map = jtl.queryForMap(sql, 1);// 2.更新String updateSql = "update from table_user set name = mjp where id = 1";int result = jtl.update(updateSql, 1);
2、源碼解析JDBCTemplate(簡(jiǎn)化版)
  • 含回調(diào)函數(shù)的接口
@FunctionalInterface
public interface StatementCallback<T> {T doInStatement(Statement stm) throws SQLException, DataAccessException;
}
  • JDBCTemplate
public <T> T execute(StatementCallback<T> action) throws DataAccessException {// 1、2加載數(shù)據(jù)庫(kù)驅(qū)動(dòng)、創(chuàng)建數(shù)據(jù)庫(kù)連接Connection con = DataSourceUtils.getConnection(this.obtainDataSource());Statement stmt = null;Object var11;try {// 3.創(chuàng)建sql語(yǔ)句執(zhí)行對(duì)象stmt = con.createStatement();this.applyStatementSettings(stmt);// 4.使用回調(diào)方法執(zhí)行stm.各種CRUDT result = action.doInStatement(stmt);this.handleWarnings(stmt);// 5.返回執(zhí)行結(jié)果,可能是對(duì)象、List<對(duì)象>、intvar11 = result;} catch (SQLException var9) {//6.關(guān)閉資源} finally {JdbcUtils.closeStatement(stmt);DataSourceUtils.releaseConnection(con, this.getDataSource());}return var11;}
其中execute方法就屬于模板方法,其中12356都是通用方法,只有4是根據(jù)不同的sql,stm執(zhí)行對(duì)應(yīng)的語(yǔ)句
  • 查詢、更新業(yè)務(wù)類
@AllArgsConstructor
public class CrudClass {private JdbcTemplate jdbcTemplate;/*** 查詢* @param sql* @return*/public ResultSet query(String sql) {return (ResultSet) jdbcTemplate.execute(new StatementCallback<Object>() {@Overridepublic Object doInStatement(Statement stm) throws SQLException, DataAccessException {ResultSet resultSet = stm.executeQuery(sql);return resultSet;}});}/*** 更新* @param sql* @return*/public Integer update(String sql) {return (Integer) jdbcTemplate.execute((StatementCallback<Object>) stm -> {int result = stm.executeUpdate(sql);return result;});}
}
  • 使用查詢、更新類
JdbcTemplate jdbcTemplate = new JdbcTemplate(DataSourceDemo.getDataSource());
CrudClass crudClass = new CrudClass(jdbcTemplate);
ResultSet resultSet = crudClass.query("select * from tb_user where id = 32");
Integer result = crudClass.update("update from tb_user set name = mjp where id = 32");
3、分析
  • 回調(diào)函數(shù)定義:A類中的a調(diào)用B類b方法時(shí),B類的b方法會(huì)反過(guò)來(lái)調(diào)用A類中注冊(cè)給它的f方法

  • JDBCTemplate應(yīng)用定義:即CrudClass類中的query調(diào)用JdbcTemplate類execute方法時(shí),execute方法會(huì)反過(guò)來(lái)調(diào)用CrudClass類中注冊(cè)給它的doInStatement方法

  • 概括:a在調(diào)用b方法時(shí),b方法的入?yún)?#xff1a;對(duì)象實(shí)例(此對(duì)象有需要Override的方法即回調(diào)方法f)

補(bǔ)充:其實(shí)真正的JDBCtemplate,又充當(dāng)了A類又充當(dāng)了B類。其中作為B類b方法即jdbcTemplate.execute和上述事例一樣,作為A類a方法則如下:

jdbcTemplate.query("", new RowMapper<Object>() {@Overridepublic Object mapRow(ResultSet resultSet, int i) throws SQLException {return null;}});
這里jdbcTemplate的query方法  

調(diào)用了jdbcTemplate類的execute方法,其中execute方法的入?yún)?#xff1a;A類(JDBCTemplate)注冊(cè)給它的StatementCallback接口的實(shí)例對(duì)象QueryStatementCallback(這個(gè)對(duì)象有需要重寫的回調(diào)方法doInStatement),回調(diào)方法內(nèi)部是各種CRUD的執(zhí)行

public <T> T query(final String sql, final ResultSetExtractor<T> rse){class QueryStatementCallback implements StatementCallback<T>, SqlProvider {QueryStatementCallback() {}@Nullablepublic T doInStatement(Statement stmt) throws SQLException {ResultSet rs = null;Object var3;try {rs = stmt.executeQuery(sql);var3 = rse.extractData(rs);} return var3;}}return this.execute((StatementCallback)(new QueryStatementCallback()));}
我們CRUDClass中使用的匿名內(nèi)部類作為對(duì)象,這里是使用的內(nèi)部類作為的對(duì)象。無(wú)論使用哪種方式,入?yún)?duì)象都有需要重寫的方法即回調(diào)方法
  • 執(zhí)行順序:

JDBCTemplate#query -->> JDBCTemplate#execute -->> 執(zhí)行步驟123 -->> 步驟4QueryStatementCallback#doInStatement#executeQuery -->> 步驟56

場(chǎng)景2 JVM Hook

1、背景

        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {@Overridepublic void run() {System.out.println("JVM程序關(guān)閉時(shí),會(huì)調(diào)用我");}}));

JVM提供了B類b即Runtime#addShutdownHook(),其中方法入?yún)榫€程對(duì)象(thread對(duì)象有需要重寫的方法即run方法即回調(diào)函數(shù))

2、作用

JVM程序關(guān)閉時(shí),會(huì)執(zhí)行回調(diào)函數(shù)

3、分析addShutdownHook

static {Shutdown.add(1 ,false,new Runnable() {public void run() {runHooks();}});}
    static void runHooks() {// 對(duì)所有的hook線程執(zhí)行startfor (Thread hook : threads) {hook.start();}}

執(zhí)行順序

Runtime#addShutdownHook() -->> static{} -->> runHooks() -->> hook.start() -->> 執(zhí)行hook線程的回調(diào)方法run方法

3.3.4策略模式:

實(shí)戰(zhàn)1
背景

業(yè)務(wù)任務(wù)有1、2、3、6、7、8 六種狀態(tài)。其中6和8是成功狀態(tài)。剩余任務(wù)狀態(tài)都是失敗狀態(tài)?,F(xiàn)在想提供一個(gè)接口可以根據(jù)任務(wù)號(hào),查詢某個(gè)失敗任務(wù)失敗的原因。

接口入?yún)槿蝿?wù)號(hào) + 失敗任務(wù)的狀態(tài)

  • 定義接口
public interface Task {TaskStatusEnum getTaskStatus();String queryTaskStatus(Integer status);
}
  • 定義接口實(shí)現(xiàn)類
@Slf4j
@Service
public class FailedTask implements Task {@Overridepublic TaskStatusEnum getTaskStatus() {return TaskStatusEnum.FAILED;}@Overridepublic String queryTaskStatus(Integer status) {return "網(wǎng)路原因計(jì)算失敗";}
}
  • 定義枚舉類(和實(shí)現(xiàn)類一一對(duì)應(yīng))
@Getter
@RequiredArgsConstructor
public enum TaskStatusEnum {INIT(1,"初始化"),FAILED(2,"失敗"),SUCCESS(3,"成功");private final Integer value;private final String desc;
}
  • 面向接口編程
@Resource
private List<Task> tasks;
private Map<TaskStatusEnum, Task> map;@PostConstruct
private void initMap() {map = tasks.stream().collect(Collectors.toMap(Task::getTaskStatus, 			Function.identity()));
}@Test
public void t() {TaskStatusEnum status = TaskStatusEnum.FAILED;Task task = map.get(status);System.out.println(task.queryTaskStatus(1));
}

這里是使用枚舉 和 實(shí)現(xiàn)類一一對(duì)應(yīng)的方式,達(dá)到set效果。

實(shí)戰(zhàn)2

1、背景

通過(guò)n分鐘內(nèi)業(yè)務(wù)告警m次來(lái)定級(jí)業(yè)務(wù)失敗的嚴(yán)重程度。不同程度的告警 有不同的處理方式

3min內(nèi)觸發(fā)2次 ==》嚴(yán)重P1 ==》 電話告警

3min內(nèi)觸發(fā)1次 ==> 緊急P2 ==》 短信告警

10min內(nèi)觸發(fā)2次 ==> 正常P3 ==》 大象告警

2、思路

規(guī)則引擎 + 策略模式

通過(guò)規(guī)則引擎來(lái)判定出嚴(yán)重程度

根據(jù)不同的嚴(yán)重程度,使用策略模式,做不同的處理

實(shí)戰(zhàn)3
  • 根據(jù)前端入?yún)?#xff0c;判斷CRUD哪個(gè)場(chǎng)景(枚舉值),執(zhí)行對(duì)應(yīng)的接口實(shí)現(xiàn)類(CRUD)
  • 根據(jù)單據(jù)的類型,判斷是調(diào)撥還是退供(1、2)在,執(zhí)行對(duì)應(yīng)的接口實(shí)現(xiàn)(逆向調(diào)撥、退供)

3.3.5 職責(zé)鏈模式

定義

一個(gè)請(qǐng)求經(jīng)過(guò)A處理器處理 -->> 然后再把請(qǐng)求傳遞給B處理器處理 -->> 再傳給C處理器。以此類推,形成一個(gè)鏈條

作用

復(fù)用、擴(kuò)展

模板
1、職責(zé)鏈模式1:帶終止

解釋:有一個(gè)處理器可以處理此請(qǐng)求,則結(jié)束整個(gè)職責(zé)鏈。后續(xù)的處理器不會(huì)再被調(diào)用

  • 抽象處理器
public interface Handle {boolean handle();
}
  • 處理器
@Service
public class HandleA implements Handle{@Overridepublic boolean handle() {// A處理器無(wú)法處理此請(qǐng)求boolean handled = false;// 自己的業(yè)務(wù)return handled;}
}
  • 職責(zé)鏈
public class HandleChain {@Resourceprivate List<Handle> handleList;public void doXxx() {for (Handle handle : handleList) {boolean canDeal = handle.handle();if (canDeal) {break;}}}
}
2、職責(zé)鏈模式2:無(wú)終止

解釋:職責(zé)鏈上的所有處理器都會(huì)依次處理此請(qǐng)求

  • 抽象處理器
public interface Handle {void handle();
}
  • 處理器
@Service
public class HandleA implements Handle{@Overridepublic void handle() {// 處理請(qǐng)求}
}
  • 職責(zé)鏈
public class HandleChain {@Resourceprivate List<Handle> handleList;public void doXxx() {for (Handle handle : handleList) {handle.handle();}}
}
實(shí)戰(zhàn):敏感詞過(guò)濾

1、背景

在文本發(fā)布時(shí),如果text中如果含有性、政治、廣告相關(guān)的關(guān)鍵字,則會(huì)被處理

處理方式一:直接禁止本次文本發(fā)布

處理方式二:過(guò)濾關(guān)鍵字違規(guī)詞后,再發(fā)布

2、處理方式一:終止型責(zé)任鏈模式

  • 抽象處理器
public interface SensitiveWordFilterHandle {boolean doFilter(String text);
}
  • 處理器
@Service
public class SexyWordFilterHandle implements SensitiveWordFilterHandle{@Overridepublic boolean doFilter(String text) {// 如果text中含有x、x、x詞,則任務(wù)含有了性相關(guān)的敏感詞。則會(huì)終止職責(zé)鏈if (true) {return true;}return false;}
}
其他處理器類似
  • 職責(zé)鏈
public class SensitiveFilterHandleChain {@ResourceList<SensitiveWordFilterHandle> sensitiveWordFilterHandles;public void legalText(String text) {for (SensitiveWordFilterHandle handle : sensitiveWordFilterHandles) {boolean legal = handle.doFilter(text);if (legal) {// 允許發(fā)布} else {// 禁止}}}
}

3、處理方式二:無(wú)終止型責(zé)任鏈模式

  • 抽象處理器
public interface SensitiveWordFilterHandle {String doFilter(String text);
}
  • 處理器
@Service
public class SexyWordFilterHandle implements SensitiveWordFilterHandle{@Overridepublic String doFilter(String text) {// 如果text包含了a、b、c等性相關(guān)詞,將這些詞替換成xxxif (true) {return text.replace(abc, "xxx");}return text;}
}
其他處理器類似
  • 職責(zé)鏈
public class SensitiveFilterHandleChain {@ResourceList<SensitiveWordFilterHandle> sensitiveWordFilterHandles;public String legalText(String text) {String temp = text;for (SensitiveWordFilterHandle handle : sensitiveWordFilterHandles) {temp = handle.doFilter(temp);}return temp;}
}
場(chǎng)景1:Servlet-Filter
作用

可以實(shí)現(xiàn)對(duì)Http的請(qǐng)求過(guò)濾(鑒權(quán)、限流、參數(shù)驗(yàn)證)、對(duì)返回結(jié)果過(guò)濾(打印日志)等

作用域

支持Servlet的Web容器(tomcat、jetty)

解析
  • 抽象處理器
public interface Filter {public default void init(FilterConfig filterConfig) throws ServletException {}public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException;public default void destroy() {}
}
  • 處理器
public class FilterHandler implements Filter {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {// 處理req// 業(yè)務(wù)chain.doFilter(request, response);// 處理resp}
}
  • 職責(zé)鏈FilterChain

FilterChain是個(gè)規(guī)范,tomcat具體實(shí)現(xiàn)是ApplicationFilterChain

public final class ApplicationFilterChain implements FilterChain {// 當(dāng)前執(zhí)行到哪個(gè)Filter處理器private int pos = 0;//Filter處理器的個(gè)數(shù)private int n = 0;//職責(zé)鏈數(shù)組private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];/*** 即chain.doFilter(request, response)方法*/@Overridepublic void doFilter(ServletRequest request, ServletResponse response)throws IOException, ServletException {// ---internalDoFilter(request,response);}private void internalDoFilter(ServletRequest request,ServletResponse response)throws IOException, ServletException {// Call the next filter if there is oneif (pos < n) {// 獲取職責(zé)鏈上的下一個(gè)Filter處理器ApplicationFilterConfig filterConfig = filters[pos++];try {// 這里就有我們具體的處理器Filter filter = filterConfig.getFilter();filter.doFilter(request, response, this);}}// 添加過(guò)濾器處理器void addFilter(ApplicationFilterConfig filterConfig) {}}
場(chǎng)景2:MVC-Interceptor
作用:同F(xiàn)ilter
作用域

MVC框架的一部分

和Servlet的Filter區(qū)別
  • Filter對(duì)req、resp的過(guò)濾都在doFilter方法中,而HandlerInterceptor對(duì)req的攔截在preHandle方法中,對(duì)resp的攔截在postHandle中,是分開(kāi)的
  • 執(zhí)行順序

http請(qǐng)求 -->> Filter -->>> doChain過(guò)濾req、resp -->> Servlet的service()中的doPost|doGet(如果自定義Servlet繼承了HttpServlet) -->> DispatcherServlet的doDsipatcher()內(nèi)含applyPreHandle|appltPostHandle -->> MVC HandlerIntercept的preHandle -->> XxxController

解析
  • 抽象攔截器處理器
public interface HandlerInterceptor {default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return true;}default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable ModelAndView modelAndView) throws Exception {}default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable Exception ex) throws Exception {}
}
  • 攔截器處理器
public class MyHandlerInterceptor implements HandlerInterceptor {public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {// 為false會(huì)攔截請(qǐng)求,true會(huì)放行// 業(yè)務(wù)邏輯// eg:根據(jù)req內(nèi)容查詢,請(qǐng)求是否合法、用戶是否存在等。如果不滿足,則請(qǐng)求被攔截掉,return falsereturn true;}public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable ModelAndView modelAndView) throws Exception {}public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable Exception ex) throws Exception {// 一定會(huì)執(zhí)行,類似finally}
}
  • 職責(zé)鏈
public class HandlerExecutionChain {// 職責(zé)鏈數(shù)組private HandlerInterceptor[] interceptors;public void addInterceptor(HandlerInterceptor interceptor) {initInterceptorList().add(interceptor);}// 攔截reqboolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {// 獲取職責(zé)鏈數(shù)組HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {for (int i = 0; i < interceptors.length; i++) {// 獲取攔截器處理器HandlerInterceptor interceptor = interceptors[i];// 攔截reqif (!interceptor.preHandle(request, response, this.handler)) {triggerAfterCompletion(request, response, null);return false;}this.interceptorIndex = i;}}return true;}
}
場(chǎng)景3:自定義職責(zé)鏈
  • 抽象處理器
public interface DefinedHandler {void doHandle(xxxReq req);void preHandle(Object req);// 執(zhí)行順序,值越小,優(yōu)先級(jí)越高default Integer executeOrder() {return Integer.MIN_VALUE;}
}
  • 處理器
@Service
public class LockStatusHandle implements DefinedHandler{@Overridepublic void preHandle(Object req) {// 處理req:比如查詢某些數(shù)據(jù)}@Overridepublic void postHandle(Object req) {// 處理resp:比如根據(jù)req過(guò)濾留下符合的數(shù)據(jù)}public Integer executeOrder() {return 10;}
}
@Service
public class AIQtyHandle implements DefinedHandler{@Overridepublic void preHandle(Object req) {// 處理req:比如查詢某些數(shù)據(jù)}@Overridepublic void postHandle(Object req) {// 處理resp:比如根據(jù)req過(guò)濾留下符合的數(shù)據(jù)}public Integer executeOrder() {return 10;}
}
@Service
public class OverStockHandle implements DefinedHandler{@Overridepublic void preHandle(Object req) {// 處理req:比如查詢某些數(shù)據(jù)}@Overridepublic void postHandle(Object req) {// 處理resp:比如根據(jù)req過(guò)濾留下符合的數(shù)據(jù)}public Integer executeOrder() {return 20;}
}
  • 職責(zé)鏈
@Component
public class HandlerChain {@Resourceprivate List<DefinedHandler> definedHandlerList;private Map<Integer, List<DefinedHandler>> orderAndHandlerMap;@PostConstructpublic void init() {// 職責(zé)鏈正排序(值越小,越先執(zhí)行。值相同的處理器一同并發(fā)執(zhí)行)definedHandlerList.sort((h1, h2) -> NumberUtils.compare(h1.executeOrder(), h2.executeOrder()));orderAndHandlerMap = definedHandlerList.stream().collect(Collectors.groupingBy(DefinedHandler::executeOrder, TreeMap::new, Collectors.toList()));}public Map<Integer, List<DefinedHandler>> getHandlerMap() {return orderAndHandlerMap;}
}
  • 使用
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ApplicationLoader.class)
@Slf4j
public class SpringTest {@Resourceprivate HandlerChain handlerChain;private ExecutorService threadPool = Executors.newFixedThreadPool(20);@Testpublic void test(){// 獲取職責(zé)鏈上所有處理器Map<Integer, List<DefinedHandler>> handlerMap = handlerChain.getHandlerMap();// 按照處理器的值大小執(zhí)行處理器(值越小的處理器,先執(zhí)行。相同值的處理器并發(fā)執(zhí)行)handlerMap.forEach((order, handlerList) ->{handlerList.stream().map(handler -> CompletableFuture.runAsync(() ->{handler.preHandle(null);}, threadPool)).collect(Collectors.toList());});}
}
  • 分析

傳統(tǒng)的職責(zé)鏈,例如Filter和HandlerInterceptor都是使用的數(shù)組存儲(chǔ)的。然后從數(shù)組中按照處理器存儲(chǔ)的index先后順序一個(gè)一個(gè)取,直到全部處理器都執(zhí)行完畢。

區(qū)別:這里的職責(zé)鏈不是數(shù)組[]存儲(chǔ),然后遍歷index獲取處理器。而是通過(guò)在抽象處理器中定義executeOrder值,每個(gè)具體的處理器,自己根據(jù)業(yè)務(wù)優(yōu)先級(jí)自定義自己的處理順序。優(yōu)先級(jí)高的處理器先執(zhí)行。相同優(yōu)先級(jí)并行執(zhí)行

3.3.6 狀態(tài)模式

定義

狀態(tài)機(jī)由3部分組成

1、狀態(tài)Status

2、事件Event

3、動(dòng)作Action

其中事件Event也被稱為狀態(tài)轉(zhuǎn)移條件。一旦觸發(fā)了事件,則一定伴隨著狀態(tài)改變Status A -> B,可能會(huì)有相應(yīng)的動(dòng)作Action產(chǎn)生

作用
  • 避免了大量if-else邏輯
  • 可擴(kuò)展性好:后續(xù)新增狀態(tài)或狀態(tài)的改變帶來(lái)動(dòng)作的執(zhí)行,只需要新增狀態(tài)類 和 動(dòng)作執(zhí)行類即可
使用場(chǎng)景

1、不建議使用場(chǎng)景

對(duì)象的狀態(tài),單行道變換。舉例:逆向任務(wù)的狀態(tài),初始化 -> 已落表 -> 已計(jì)算 -> 成功|部分成功|失敗

2、建議使用場(chǎng)景

當(dāng)一個(gè)對(duì)象的狀態(tài)<=5,而且狀態(tài)之間可以相互轉(zhuǎn)換,當(dāng)達(dá)到不同的狀態(tài)會(huì)產(chǎn)生不同的動(dòng)作時(shí),建議使用狀態(tài)模式

實(shí)戰(zhàn)-電商訂單
1、狀態(tài)機(jī)

在這里插入圖片描述

2、實(shí)現(xiàn)

2.1 抽象狀態(tài)類

public interface OrderStatus {
}

2.2 狀態(tài)類

  • 待付款
/*** 待付款狀態(tài)類* 此狀態(tài)類狀態(tài)的可能轉(zhuǎn)移為:*      待付款 -> 取消付款(用戶點(diǎn)擊了取消付款按鈕)*      待付款 -> 待發(fā)貨(用戶付款了)*/
public class WaitPay implements OrderStatus {private static final WaitPay instance = new WaitPay();private WaitPay(){}public static WaitPay getInstance() {System.out.println("訂單生成,30分鐘內(nèi)有效");return instance;}/*** 事件Event:用戶取消付款了*/public void cancelPay(OrderStatusMachine machine) {System.out.println("===Event:用戶取消了付款===");// 狀態(tài)Status: 待付款 -> 取消付款machine.setOrderStatus(CancelPay.getInstance());// 動(dòng)作ActionSystem.out.println("更細(xì)訂單狀態(tài)為:已取消");}/*** 事件Event:用戶付款了*/public void clickPay(OrderStatusMachine machine) {System.out.println("===Event:用戶付款了===");// 狀態(tài)Status: 待付款 -> 待發(fā)貨machine.setOrderStatus(WaitDeliver.getInstance());// 動(dòng)作ActionSystem.out.println("1、更細(xì)訂單狀態(tài)為:待發(fā)貨");System.out.println("2、將錢存入支付寶");System.out.println("3、淘寶信息提醒:您的地址信息為xxx請(qǐng)核對(duì)");}
}
  • 取消付款
/*** 取消狀態(tài)類* 此狀態(tài)為終態(tài),不會(huì)再轉(zhuǎn)移:*/
public class CancelPay implements OrderStatus {private static final CancelPay instance = new CancelPay();private CancelPay(){}public static CancelPay getInstance() {return instance;}
}
  • 待發(fā)貨
/*** 待發(fā)貨狀態(tài)類* 此狀態(tài)類狀態(tài)的可能轉(zhuǎn)移為:*      待發(fā)貨 -> 退貨退款(用戶點(diǎn)擊了申請(qǐng)退貨退款按鈕)*      待發(fā)貨 -> 待收貨(倉(cāng)庫(kù)提交發(fā)貨)*/
public class WaitDeliver implements OrderStatus{private static final WaitDeliver instance = new WaitDeliver();private WaitDeliver(){}public static WaitDeliver getInstance() {return instance;}/*** 事件Event:用戶點(diǎn)擊了申請(qǐng)退貨退款按鈕*/public void waitDeliverApplyRefund(OrderStatusMachine machine) {System.out.println("===Event:用戶申請(qǐng)了退貨退款===");// 狀態(tài)Status: 待發(fā)貨 -> 退貨退款machine.setOrderStatus(Refund.getInstance());// 2.動(dòng)作System.out.println("1、更新訂單狀態(tài)為:退貨退款");System.out.println("2、支付寶將錢原路退回給用戶");}/*** 事件Event:倉(cāng)庫(kù)提交發(fā)貨*/public void submitDelivery(OrderStatusMachine machine) {System.out.println("===Event:倉(cāng)庫(kù)提交了發(fā)貨===");// 狀態(tài)Status: 待發(fā)貨 -> 待收貨machine.setOrderStatus(WaitReceive.getInstance());// 2.動(dòng)作System.out.println("1、更細(xì)訂單狀態(tài)為:待收貨");System.out.println("2、發(fā)送信息給用戶:您的包裹正在快馬加鞭的趕來(lái)");}
}
  • 待收貨
/*** 待收貨狀態(tài)類* 此狀態(tài)類狀態(tài)的可能轉(zhuǎn)移為:*      待收貨 -> 退貨退款(用戶點(diǎn)擊了申請(qǐng)退貨退款按鈕)*      待收貨 -> 待評(píng)價(jià)(確認(rèn)收貨)*/
public class WaitReceive implements OrderStatus{private static final WaitReceive instance = new WaitReceive();private WaitReceive(){}public static WaitReceive getInstance() {return instance;}/*** 事件Event:用戶點(diǎn)擊了申請(qǐng)退貨退款按鈕*/public void waitReceiveApplyRefund(OrderStatusMachine machine) {System.out.println("===Event:用戶申請(qǐng)了退貨退款===");// 狀態(tài)Status: 待收貨 -> 退貨退款machine.setOrderStatus(Refund.getInstance());// 2.動(dòng)作System.out.println("1、更新訂單狀態(tài)為:退貨退款");System.out.println("2、支付寶將錢原路退回給用戶");}/*** 事件Event:用戶確認(rèn)收貨*/public void confirmReceive(OrderStatusMachine machine) {System.out.println("===Event:用戶確認(rèn)了收貨===");// 狀態(tài): 待收貨 -> 待評(píng)價(jià)machine.setOrderStatus(WaitReview.getInstance());// 動(dòng)作System.out.println("1、更細(xì)訂單狀態(tài)為:待評(píng)價(jià)");System.out.println("2、發(fā)送信息給用戶:親,麻煩評(píng)價(jià)下商品");}
}
  • 退貨退款
/*** 退貨退款狀態(tài)類* 此狀態(tài)為終態(tài),不會(huì)再轉(zhuǎn)移:*/
public class Refund implements OrderStatus{private static final Refund instance = new Refund();private Refund(){}public static Refund getInstance() {return instance;}
}
  • 待評(píng)價(jià)
/*** 待評(píng)價(jià)狀態(tài)類* 此狀態(tài)類狀態(tài)的可能轉(zhuǎn)移為:*      待評(píng)價(jià) -> 訂單完成(用戶評(píng)價(jià)了商品)*/
public class WaitReview implements OrderStatus{private static final WaitReview instance = new WaitReview();private WaitReview(){}public static WaitReview getInstance() {return instance;}/*** 事件Event:用戶評(píng)價(jià)商品*/public void reviewGoods(OrderStatusMachine machine) {System.out.println("===Event:用戶評(píng)價(jià)了商品===");// 狀態(tài): 待評(píng)價(jià) -> 完成machine.setOrderStatus(Finish.getInstance());// 動(dòng)作System.out.println("1、更細(xì)訂單狀態(tài)為:已完成");System.out.println("2、支付寶將錢打給商家");System.out.println("3、用戶的積分增加");}
}
  • 訂單完成
/*** 完成狀態(tài)類* 此狀態(tài)為終態(tài),不會(huì)再轉(zhuǎn)移:*/
public class Finish implements OrderStatus{private static final Finish instance = new Finish();private Finish(){}public static Finish getInstance() {return instance;}
}

2.3 狀態(tài)機(jī)

每個(gè)非終態(tài)的狀態(tài)類中的方法,都需要在狀態(tài)機(jī)中定義下

@Data
public class OrderStatusMachine {private OrderStatus orderStatus;public OrderStatusMachine(){this.orderStatus = WaitPay.getInstance();}/*** 事件Event:用戶取消付款了*/public void cancelPay() {((WaitPay) this.orderStatus).cancelPay(this);}/*** 事件Event:用戶付款了*/public void clickPay() {((WaitPay) this.orderStatus).clickPay(this);}/*** 事件Event:待發(fā)貨時(shí),用戶點(diǎn)擊了申請(qǐng)退貨退款*/public void waitDeliverApplyRefund() {((WaitDeliver) this.orderStatus).waitDeliverApplyRefund(this);}/*** 事件Event:用戶確認(rèn)收貨*/public void submitDelivery() {((WaitDeliver) this.orderStatus).submitDelivery(this);}/*** 事件Event:待收貨時(shí),用戶點(diǎn)擊了申請(qǐng)退貨退款*/public void waitReceiveApplyRefund() {((WaitReceive) this.orderStatus).waitReceiveApplyRefund(this);}/*** 事件Event:用戶評(píng)價(jià)商品*/public void confirmReceive() {((WaitReceive) this.orderStatus).confirmReceive(this);}/*** 事件Event:用戶評(píng)價(jià)商品*/public void reviewGoods() {// 狀態(tài): 待評(píng)價(jià) -> 訂單完成((WaitReview) this.orderStatus).reviewGoods(this);}
}

2.4 使用狀態(tài)機(jī)

@RunWith(MockitoJUnitRunner.class)
@Slf4j
public class BaseTest {@Testpublic void test() {System.out.println("==================李四的訂單start===========");OrderStatusMachine m1 = new OrderStatusMachine();// 1.李四(付款 -> 倉(cāng)庫(kù)發(fā)貨 -> 確認(rèn)收貨 -> 評(píng)價(jià))m1.clickPay();m1.submitDelivery();m1.confirmReceive();m1.reviewGoods();System.out.println("==================李四的訂單end===========");System.out.println("==================王五的訂單start==================");OrderStatusMachine m2 = new OrderStatusMachine();// 2.王五(付款 -> 退貨退款)m2.clickPay();m2.waitDeliverApplyRefund();System.out.println("==================王五的訂單end==================");System.out.println("==================趙六的訂單start==================");OrderStatusMachine m3 = new OrderStatusMachine();// 3.趙六(付款 -> 倉(cāng)庫(kù)發(fā)貨 -> 退貨退款)m3.clickPay();m3.submitDelivery();m3.waitReceiveApplyRefund();System.out.println("==================趙六的訂單end==================");}
}
3、總結(jié)

3.1 狀態(tài)機(jī)中

    public void cancelPay() {((WaitPay) this.orderStatus).cancelPay(this);}

如果不這樣寫,那么對(duì)于原狀態(tài)為其他的比如待發(fā)貨狀態(tài),不會(huì)有任何Event事件,更不會(huì)有Action動(dòng)作,但是發(fā)貨狀態(tài)類也需要定義WaitPay方法,只不過(guò)方法內(nèi)沒(méi)東西

public class WaitDeliver implements OrderStatus{private static final WaitDeliver instance = new WaitDeliver();private WaitDeliver(){}public static WaitDeliver getInstance() {return instance;}// 因?yàn)閷?duì)于WaitDeliver代發(fā)貨狀態(tài)而言,他沒(méi)有取消付款事件,所以定義個(gè)空方法。// 同理,他也沒(méi)有評(píng)價(jià)事件。public void cancelPay() {}/*** 事件Event:用戶點(diǎn)擊了申請(qǐng)退貨退款按鈕*/public void waitDeliverApplyRefund(OrderStatusMachine machine) {System.out.println("===Event:用戶申請(qǐng)了退貨退款===");// 狀態(tài)Status: 待發(fā)貨 -> 退貨退款machine.setOrderStatus(Refund.getInstance());// 2.動(dòng)作System.out.println("1、更新訂單狀態(tài)為:退貨退款");System.out.println("2、支付寶將錢原路退回給用戶");}/*** 事件Event:倉(cāng)庫(kù)提交發(fā)貨*/public void submitDelivery(OrderStatusMachine machine) {System.out.println("===Event:倉(cāng)庫(kù)提交了發(fā)貨===");// 狀態(tài)Status: 待發(fā)貨 -> 待收貨machine.setOrderStatus(WaitReceive.getInstance());// 2.動(dòng)作System.out.println("1、更細(xì)訂單狀態(tài)為:待收貨");System.out.println("2、發(fā)送信息給用戶:您的包裹正在快馬加鞭的趕來(lái)");}
}

這樣寫的好處是:只有原狀態(tài)為:待付款狀態(tài),才會(huì)有Event取消付款,才會(huì)有后續(xù)Action動(dòng)作。其他狀態(tài)類不需要關(guān)注,內(nèi)部也不需要定義此方法

3.2 單例

private static final WaitDeliver instance = new WaitDeliver();

防止反復(fù)創(chuàng)建

3.3 擴(kuò)展

對(duì)于像WaitPay狀態(tài)類,當(dāng)用戶付款了后,狀態(tài)變?yōu)?#xff1a;待發(fā)貨。然后有好幾個(gè)總做要去做

    /*** 事件Event:用戶付款了*/public void clickPay(OrderStatusMachine machine) {System.out.println("===Event:用戶付款了===");// 狀態(tài)Status: 待付款 -> 待發(fā)貨machine.setOrderStatus(WaitDeliver.getInstance());// 動(dòng)作ActionSystem.out.println("1、更細(xì)訂單狀態(tài)為:待發(fā)貨");System.out.println("2、將錢存入支付寶");System.out.println("3、淘寶信息提醒:您的地址信息為xxx請(qǐng)核對(duì)");}

當(dāng)然這3個(gè)動(dòng)作都可以在clickPay方法中定義,但是如果后續(xù),用戶付款后,新增加動(dòng)作,或者原動(dòng)作不執(zhí)行了。則需要改clickPay中代碼,違背了開(kāi)閉原則。

  • 解決:責(zé)任鏈模式

參考責(zé)任鏈模式中場(chǎng)景3:自定義責(zé)任鏈??梢愿鶕?jù)動(dòng)作的執(zhí)行先后,對(duì)應(yīng)處理器的先后執(zhí)行。

這樣不同的動(dòng)作,都實(shí)現(xiàn)了抽象動(dòng)作,這樣新增、刪除等,直接增加對(duì)應(yīng)的動(dòng)作 - 處理器即可,滿足開(kāi)閉原則。

補(bǔ)充:前提是,clickPay事件Event觸發(fā)后,動(dòng)作確認(rèn)存在頻繁變動(dòng)的場(chǎng)景

3.3.7 解釋器模式

定義

描述如何 構(gòu)建一個(gè)簡(jiǎn)單的"語(yǔ)言"解釋器

作用

解釋自定義“語(yǔ)言”

實(shí)戰(zhàn)
背景

開(kāi)發(fā)一個(gè)監(jiān)控業(yè)務(wù)系統(tǒng)。當(dāng)每分鐘接口出錯(cuò)數(shù)目超過(guò)10,或者每分鐘API的調(diào)用總數(shù)大于10w,則觸發(fā)告警。當(dāng)然具體的告警可以是短信、電話等

分析

我們可以把自定義的告警規(guī)則當(dāng)做一種“語(yǔ)言”的語(yǔ)法規(guī)則,然后實(shí)現(xiàn)一個(gè)解釋器即可。

針對(duì)用戶的輸入,判斷是否觸發(fā)告警

實(shí)現(xiàn)
  • 舉例:

String exp = “key1 > 100 && key2 > 100000 || key3 == 404”;

表達(dá)式含義:

key1即每分鐘的錯(cuò)誤數(shù)大于100 ,同時(shí)key2即每分鐘的接口調(diào)用量大于10w

或者即key3接口返回404

上述場(chǎng)景則返回true,需要告警

  • 使用
    @Testpublic void test() {String exp = "key1 > 100 && key2 > 100000 || key3 == 404";Expression expression = RuleExpressionFactory.getExpression(exp);Map<String, Long> map = new HashMap<>();map.put("key1", 200L);map.put("key2", 1000L);map.put("key3", 404L);boolean intercept = expression.intercept(map);System.out.println(intercept);}
  • 解釋器接口
public interface Expression {boolean intercept(Map<String , Long> map);
}
  • 大于解釋器
public class GreaterExpression implements Expression{private String key;private Long value;public GreaterExpression(String strExpression) {String[] elements = strExpression.trim().split("\\s+");this.key = elements[0].trim();this.value = NumberUtils.toLong(elements[2].trim());}public GreaterExpression(String key, Long value) {this.key = key;this.value = value;}@Overridepublic boolean intercept(Map<String, Long> map) {if (!map.containsKey(key)) {return false;}Long val = map.get(key);return val > value;}
}
  • 小于解釋器
public class LessExpression implements Expression{private String key;private Long value;public LessExpression(String strExpression) {String[] elements = strExpression.trim().split("\\s+");this.key = elements[0].trim();this.value = NumberUtils.toLong(elements[2].trim());}public LessExpression(String key, Long value) {this.key = key;this.value = value;}@Overridepublic boolean intercept(Map<String, Long> map) {if (!map.containsKey(key)) {return false;}Long val = map.get(key);return val < value;}
}
  • 等于解釋器
public class EqualsExpression implements Expression{private String key;private Long value;public EqualsExpression(String strExpression) {String[] elements = strExpression.trim().split("\\s+");this.key = elements[0].trim();this.value = NumberUtils.toLong(elements[2].trim());}public EqualsExpression(String key, Long value) {this.key = key;this.value = value;}@Overridepublic boolean intercept(Map<String, Long> map) {if (!map.containsKey(key)) {return false;}Long val = map.get(key);return Objects.equals(val, value);}
}
  • &&解釋器
public class AndExpression implements Expression{private List<Expression> expressionList = new ArrayList<>();public AndExpression(String expression) {String[] elements = expression.split("&&");for (String ele : elements) {if (ele.contains("||")) {expressionList.add(new OrExpression(ele));} else if (ele.contains(">")) {expressionList.add(new GreaterExpression(ele));} else if (ele.contains("<")) {expressionList.add(new LessExpression(ele));} else if (ele.contains("==")) {expressionList.add(new EqualsExpression(ele));} else {throw new RuntimeException("錯(cuò)誤的表達(dá)式");}}}public AndExpression(List<Expression> expressionList) {this.expressionList.addAll(expressionList);}@Overridepublic boolean intercept(Map<String, Long> map) {for (Expression expression : expressionList) {if (expression.intercept(map)) {System.out.println(expression + "符合表達(dá)式");} else {return false;}}return true;}
}
  • ||解釋器
public class OrExpression implements Expression{private List<Expression> expressionList = new ArrayList<>();public OrExpression(String expression) {String[] elements = expression.split("\\|\\|");for (String exp : elements) {Expression ruleExpression = RuleExpressionFactory.getExpression(exp);expressionList.add(ruleExpression);}}public OrExpression(List<Expression> expressionList) {this.expressionList.addAll(expressionList);}@Overridepublic boolean intercept(Map<String, Long> map) {for (Expression expression : expressionList) {if (expression.intercept(map)) {return true;}}return false;}
}
  • 解釋器工廠類
@UtilityClass
public class RuleExpressionFactory {public Expression getExpression(String exp) {if (exp.contains("&&")) {return new AndExpression(exp);} else if (exp.contains("||")) {return new OrExpression(exp);} else if (exp.contains(">")) {return new GreaterExpression(exp);} else if (exp.contains("<")) {return new LessExpression(exp);} else if (exp.contains("==")) {return new EqualsExpression(exp);}throw new RuntimeException();}
}
場(chǎng)景

編譯器、規(guī)則引擎(這里舉例阿里的規(guī)則引擎QLExpress)、正則表達(dá)式

1、地址:https://github.com/alibaba/QLExpress

        <dependency><groupId>com.alibaba</groupId><artifactId>QLExpress</artifactId><version>3.3.2</version></dependency>

2、 eg1:

    @Testpublic void t() throws Exception {ExpressRunner runner = new ExpressRunner();DefaultContext<String, Object> context = new DefaultContext<>();context.put("a", Boolean.TRUE);context.put("l", Boolean.TRUE);context.put("lo", Boolean.TRUE);context.put("s", Boolean.FALSE);String express = "a&&l&&lo&&s";//false
//        String express = "a&&llo||s"; //trueObject res = runner.execute(express, context, null, true, false);System.out.println(res);}

eg2:

    @Testpublic void t() throws Exception {DefaultContext<String, MetaRuleResult> context = new DefaultContext<>();context.put("o", MetaRuleResult.builder().skuId(1L).result(true).metaRule("o").failureReason("").build());context.put("l", MetaRuleResult.builder().skuId(1L).result(false).metaRule("l").failureReason("鎖庫(kù)存不可更改").build());context.put("s", MetaRuleResult.builder().skuId(1L).result(true).metaRule("s").failureReason("").build());context.put("w", MetaRuleResult.builder().skuId(1L).result(false).metaRule("w").failureReason("售罄預(yù)警不可更改").build());context.put("lo", MetaRuleResult.builder().skuId(1L).result(true).metaRule("lo").failureReason("").build());context.put("llo", MetaRuleResult.builder().skuId(1L).result(false).metaRule("llo").failureReason("鎖庫(kù)且修改值小于等于OR值可以更改").build());ExpressRunner runner = new ExpressRunner();Object result;DefaultContext<String, Object> computeContext = new DefaultContext<>();for (Map.Entry<String, MetaRuleResult> entry : context.entrySet()) {computeContext.put(entry.getKey(), entry.getValue().getResult());}String ruleExpress = "o&&l&&s&&w&&lo&&llo";result = runner.execute(ruleExpress, computeContext, null, true, false);Boolean bResult = (Boolean) result;System.out.println(bResult);//falseString failReason = buildFailureReason(ruleExpress, context);System.out.println(failReason);//售罄預(yù)警且鎖庫(kù)存不可更改且鎖庫(kù)且修改值小于等于OR值可以更改}private String buildFailureReason(String ruleExpress, DefaultContext<String, MetaRuleResult> context) {StringBuilder sb = new StringBuilder();sb.append("修改失敗原因如下:");context.forEach((rule, meta) -> {if (! meta.getResult()) {sb.append(meta.getFailureReason() + "; ");}});return sb.toString();}
  • context

    key為規(guī)則rule內(nèi)容eg:”a“允許、"llo"鎖庫(kù)且小于OR允許、"s"即20點(diǎn)后修改值小于可履約庫(kù)存允許修改,

    value為,rpc查詢依賴的各個(gè)數(shù)據(jù),判斷當(dāng)前sku,是否滿足這個(gè)規(guī)則,滿足為true,不滿足為false

    eg:key = “s”,此時(shí)為20點(diǎn)后修改最大售賣量,想把最大售賣量從50 -> 30,計(jì)算發(fā)現(xiàn)可履約庫(kù)存為40,30 < 40,則允許修改,即MetaRuleResult的result值為true

  • express

    表達(dá)式為修改規(guī)則的組合;

    db中規(guī)則為"a&&llo"、"llo&&s&&t"等等

  • execute執(zhí)行

    當(dāng)滿足所有的規(guī)則,全部為true,則本次此sku允許修改最大售賣量,一個(gè)規(guī)則不滿足,最終結(jié)果res都會(huì)為false,不允許修改最大售賣量

    然后將每個(gè)規(guī)則,不滿足的原因都記錄下來(lái) ”且“,返回給檔期展示即可。

3.3.8 中介模式

定義

定義一個(gè)中介對(duì)象,用其來(lái)封裝一組對(duì)象之間的交互。

將這組對(duì)象之間的交互 =》 這組對(duì)象與中介的交互。

作用
  • 避免這組對(duì)象之間的大量直接交互。解耦這組對(duì)象之間的交互
  • 將一組對(duì)象之間的交互圖從網(wǎng)狀關(guān)系,轉(zhuǎn)換為星狀
場(chǎng)景

航空管制:參與者之間的交互關(guān)系錯(cuò)綜復(fù)雜,維護(hù)成本很高。

背景

為了讓飛機(jī)在飛行的時(shí)候互相不干擾,每架飛機(jī)必須知道其他飛機(jī)每時(shí)每刻的位置,這樣就需要飛機(jī)之間時(shí)刻通信。通信網(wǎng)絡(luò)就會(huì)非常復(fù)雜

中介模式
  • 中介:塔臺(tái)
  • 一組對(duì)象:飛機(jī)們
  • 通信:采用星形關(guān)系,每架飛機(jī)只和塔臺(tái)交互,發(fā)送自己的位置給塔臺(tái),由中介來(lái)負(fù)責(zé)每架飛機(jī)的航線調(diào)度,這樣就大大簡(jiǎn)化了對(duì)象們之間的交互
  • 風(fēng)險(xiǎn):中介類可能變得異常復(fù)雜且龐大

四、其它

4.1 系統(tǒng)設(shè)計(jì)

4.1.1合理將功能劃分到不同模塊

  • eg:逆向計(jì)劃中觸發(fā)模塊、計(jì)算模塊、合單下發(fā)模塊。模塊內(nèi)部高內(nèi)聚,模塊之間MQ交互低耦合

  • 如何判斷模塊的劃分是否合理

    如果某個(gè)功能的修改或添加,經(jīng)常需要跨系統(tǒng)才能完成,說(shuō)明模劃分不合理,職責(zé)不清晰,耦合嚴(yán)重。

    • good case:計(jì)劃側(cè)定量、oih定向
    • bad case:逆向計(jì)劃 和 frc觸發(fā)模塊。原本鏈路為:前端 -> 物流 -> frc -> 計(jì)劃。鏈路長(zhǎng),重試機(jī)制,校驗(yàn)機(jī)制,狀態(tài)一致性復(fù)雜。重構(gòu)后:前端 -> 計(jì)劃

4.1.2模塊之間的交互關(guān)系

  • 同步:接口(上下游)
  • 異步:MQ(同層、系統(tǒng)內(nèi)部)
  • 也可以同步接口調(diào)用下游,下游簡(jiǎn)單校驗(yàn)req,然后返回resp。然后下游內(nèi)部處理完成后,MQ再回掉我們本次請(qǐng)求的結(jié)果

4.1.3業(yè)務(wù)開(kāi)發(fā)

  • 接口:設(shè)計(jì)原則 + 設(shè)計(jì)模式

  • 業(yè)務(wù)邏輯:

    • POExample放在Repository層,不要出現(xiàn)在Service業(yè)務(wù)邏輯層;Mapper中,只寫CRUD;Repository中通過(guò)構(gòu)建example調(diào)用Mapper的CRUD

    Service中對(duì)查詢出的PO,進(jìn)行Convert成BO|DTO

    • 接口的TReq不要滲透到底層

    • 每一層,只提供最基本的查詢。至于查詢的結(jié)處理成什么樣,由上一層調(diào)用方自己決定。這樣,每一層的方法復(fù)用性才更好。

      • 網(wǎng)關(guān)層,只寫rpc查詢。并發(fā)查詢,業(yè)務(wù)側(cè)自己封裝
      • Repository只寫查詢出的List list。Service自己對(duì)list進(jìn)行convert2DTO或別的
  • 數(shù)據(jù)體

    • PO -> BO|DTO -> VO
    • PO(id、name、age、sex、pwd、edu) -> BO(name, age, sex, edu) -> DTO(“mjp”, 1, “man”, “大學(xué)”) -> VO(“mjp”, “青年”, “男士”, “本科”)
    • BO 和 DTO基本等效 ; 數(shù)據(jù)需要返回給前端且大量字段需要后端優(yōu)化展示內(nèi)容才需要VO,正常DTO即可

4.2 重構(gòu)

4.2.1what

在保持功能不變的前提下,利用設(shè)計(jì)原則、設(shè)計(jì)模式等理論,來(lái)修改設(shè)計(jì)上的不足 和 提高代碼質(zhì)量

4.1.2why

  • 避免一開(kāi)始的過(guò)度設(shè)計(jì)
  • 運(yùn)用模式

初級(jí):在原有框架下寫

高級(jí):從零開(kāi)始設(shè)計(jì)代碼結(jié)構(gòu),搭建代碼框架

資深:發(fā)覺(jué)代碼、框架等存在的問(wèn)題,重構(gòu)

4.1.3 context

1、大型重構(gòu)

  • 涉及到的面

    架構(gòu)(商品、物流)、模塊(觸發(fā)、計(jì)算、合單下發(fā))、代碼結(jié)構(gòu)(單據(jù)和單據(jù)明細(xì))、類之間的交互(同步、異步)

  • how如何大型重構(gòu)

    • 使用設(shè)計(jì)思想、原則 和模式。常見(jiàn)手段有
      • 模塊化(計(jì)劃內(nèi)部劃分為3個(gè)模塊)
      • 解耦(計(jì)劃內(nèi)部,通過(guò)MQ解耦)
      • 抽象可復(fù)用(單據(jù)下發(fā)抽象成接口)
      • 分層(Controller、Service、Dao)
    • 解耦手段:封裝、抽象、模塊化、中間層
      • 作用
      • 哪些代碼需要解耦
      • 如何解耦
    • 列出計(jì)劃,分階段重構(gòu)
      • 每個(gè)階段完成一小部分代碼的重構(gòu),然后UT,再繼續(xù)下一階段的重構(gòu),讓代碼始終可運(yùn)行
      • 每個(gè)階段要控制重構(gòu)影響的代碼范圍,考慮好新老兼容。切記不可無(wú)計(jì)劃重構(gòu)

2、小型重構(gòu)

  • 涉及到的面
    • 類(單一職責(zé)、提取公共 代碼)
    • 函數(shù)(里氏替換原則:Repository中方法只寫查詢,具體查詢到的結(jié)果交于上層自己去convert,這樣方法復(fù)用性更好)
    • 變量等(代碼規(guī)范)

4.1.4 when

  • 新需求,涉及對(duì)老代碼的改動(dòng)。如果時(shí)間充足 + 老代碼設(shè)計(jì)或質(zhì)量存在問(wèn)題,則重構(gòu)
  • 重構(gòu)意識(shí),把重構(gòu)作為開(kāi)發(fā)的一部分,成為一種習(xí)慣。對(duì)自己和代碼都好

4.1.5 如何避免重構(gòu)出問(wèn)題

UT

  • 本身也是一次CR
  • 代碼的可測(cè)試性,從側(cè)面也反映出代碼的設(shè)計(jì)是否合理
ecute(express, context, null, true, false);System.out.println(res);}

4.2 重構(gòu)

4.2.1what

在保持功能不變的前提下,利用設(shè)計(jì)原則、設(shè)計(jì)模式等理論,來(lái)修改設(shè)計(jì)上的不足 和 提高代碼質(zhì)量

4.2.2why

  • 避免一開(kāi)始的過(guò)度設(shè)計(jì)
  • 運(yùn)用模式

初級(jí):在原有框架下寫

高級(jí):從零開(kāi)始設(shè)計(jì)代碼結(jié)構(gòu),搭建代碼框架

資深:發(fā)覺(jué)代碼、框架等存在的問(wèn)題,重構(gòu)

4.2.3 context

1、大型重構(gòu)

  • 涉及到的面

    架構(gòu)(逆向鏈路:商品、物流)、模塊(觸發(fā)、計(jì)算、合單下發(fā))、代碼結(jié)構(gòu)(單據(jù)和單據(jù)明細(xì))、類之間的交互(同步、異步)

  • how如何大型重構(gòu)

    • 使用設(shè)計(jì)思想、原則 和模式。常見(jiàn)手段有
      • 模塊化(逆向計(jì)劃內(nèi)部劃分為3個(gè)模塊)
      • 解耦(逆向計(jì)劃內(nèi)部,通過(guò)MQ解耦)
      • 抽象可復(fù)用(單據(jù)下發(fā)抽象成接口)
      • 分層(Controller、Service、Dao)
    • 解耦手段:封裝、抽象、模塊化、中間層
      • 作用
      • 哪些代碼需要解耦
      • 如何解耦
    • 列出計(jì)劃,分階段重構(gòu)
      • 每個(gè)階段完成一小部分代碼的重構(gòu),然后UT,再繼續(xù)下一階段的重構(gòu),讓代碼始終可運(yùn)行
      • 每個(gè)階段要控制重構(gòu)影響的代碼范圍,考慮好新老兼容。切記不可無(wú)計(jì)劃重構(gòu)

2、小型重構(gòu)

  • 涉及到的面
    • 類(單一職責(zé)、提取公共 代碼)
    • 函數(shù)(里氏替換原則:Repository中方法只寫查詢,具體查詢到的結(jié)果交于上層自己去convert,這樣方法復(fù)用性更好)
    • 變量等(代碼規(guī)范)

4.2.4 when

  • 新需求,涉及對(duì)老代碼的改動(dòng)。如果時(shí)間充足 + 老代碼設(shè)計(jì)或質(zhì)量存在問(wèn)題,則重構(gòu)
  • 重構(gòu)意識(shí),把重構(gòu)作為開(kāi)發(fā)的一部分,成為一種習(xí)慣。對(duì)自己和代碼都好

4.2.5 如何避免重構(gòu)出問(wèn)題

UT

  • 本身也是一次CR
  • 代碼的可測(cè)試性,從側(cè)面也反映出代碼的設(shè)計(jì)是否合理
http://www.risenshineclean.com/news/62932.html

相關(guān)文章:

  • 高陵微網(wǎng)站建設(shè)網(wǎng)絡(luò)服務(wù)器地址怎么查
  • 河北網(wǎng)站建設(shè)團(tuán)隊(duì)如何建立網(wǎng)站 個(gè)人
  • 如何在自己電腦上做網(wǎng)站服務(wù)器夜狼seo
  • 武漢h5網(wǎng)站建設(shè)百度一下app下載安裝
  • 網(wǎng)站制作流程有哪些一站式網(wǎng)絡(luò)營(yíng)銷
  • 建e網(wǎng)模型官網(wǎng)seo快速排名培訓(xùn)
  • 公司建設(shè)官方網(wǎng)站需要多少錢做運(yùn)營(yíng)的具體做什么
  • ps海報(bào)素材網(wǎng)站先做后付費(fèi)的代運(yùn)營(yíng)
  • 做二手車的網(wǎng)站有哪些外貿(mào)營(yíng)銷網(wǎng)站
  • wordpress表白模板下載seo推廣平臺(tái)
  • 中國(guó)flash網(wǎng)站模板中心西安百度推廣優(yōu)化
  • apple開(kāi)發(fā)者中心商品標(biāo)題seo是什么意思
  • 酷炫網(wǎng)站模板抖音seo供應(yīng)商
  • b站做簡(jiǎn)介的網(wǎng)站中央電視臺(tái)一套廣告價(jià)目表
  • DW做旅游網(wǎng)站畢業(yè)設(shè)計(jì)精準(zhǔn)網(wǎng)絡(luò)推廣
  • 用網(wǎng)站做自我介紹pptseo教程免費(fèi)
  • 培訓(xùn)網(wǎng)站開(kāi)發(fā)機(jī)構(gòu)免費(fèi)網(wǎng)站大全
  • 南寧手機(jī)網(wǎng)站建設(shè)網(wǎng)站seo診斷
  • 贛州那里有做網(wǎng)站的公司cba排名
  • 提供建站服務(wù)的網(wǎng)絡(luò)公司的比較培訓(xùn)
  • 網(wǎng)站建設(shè)學(xué)習(xí)心得愛(ài)站網(wǎng)seo
  • 做貿(mào)易進(jìn)出口要什么網(wǎng)站平臺(tái)網(wǎng)絡(luò)銷售好做嗎
  • 怎么申請(qǐng) 免費(fèi)網(wǎng)站站長(zhǎng)工具排行榜
  • 企業(yè)網(wǎng)站建設(shè)空間如何免費(fèi)推廣網(wǎng)站
  • 公司網(wǎng)站 用 個(gè)人備案域名藝考培訓(xùn)學(xué)校
  • 購(gòu)買網(wǎng)站空間的方法南寧seo公司
  • 服務(wù)器網(wǎng)站怎么用360渠道推廣系統(tǒng)
  • 做外貿(mào)網(wǎng)站要花多少錢怎么制作網(wǎng)頁(yè)教程
  • 可以做動(dòng)效的網(wǎng)站產(chǎn)品網(wǎng)絡(luò)營(yíng)銷策劃方案
  • 51一起做網(wǎng)站0元做游戲代理