https:// 
 
 
 沉淀、分享、成長,讓自己和他人都能有所收獲!😄
 
  
代碼一把梭,兄弟來背鍋。
 
大部分做開發(fā)的小伙伴初心都希望把代碼寫好,除了把編程當(dāng)作工作以外他們還是具備工匠精神的從業(yè)者。但很多時候又很難讓你把初心堅持下去,就像;接了個爛手的項目、產(chǎn)品功能要的急、個人能力不足,等等原因?qū)е鹿こ檀a臃腫不堪,線上頻出事故,最終離職走人。
 
看了很多書、學(xué)了很多知識,多線程能玩出花,可最后我還是寫不好代碼!
 
這就有點像家里裝修完了買物件,我?guī)资f的實木沙發(fā),怎么放這里就不好看。同樣代碼寫的不好并不一定是基礎(chǔ)技術(shù)不足,也不一定是產(chǎn)品要得急 怎么實現(xiàn)我不管明天上線。而很多時候是我們對編碼的經(jīng)驗的不足和對架構(gòu)的把控能力不到位,我相信產(chǎn)品的第一個需求往往都不復(fù)雜,甚至所見所得。但如果你不考慮后續(xù)的是否會拓展,將來會在哪些模塊繼續(xù)添加功能,那么后續(xù)的代碼就會隨著你種下的第一顆惡性的種子開始蔓延。
 
學(xué)習(xí)設(shè)計模式的心得有哪些,怎么學(xué)才會用!
 
設(shè)計模式書籍,有點像考駕駛證的科一、家里裝修時的手冊、或者單身狗的戀愛寶典。但!你只要不實操,一定能搞的亂碼七糟 。因為這些指導(dǎo)思想都是從實際經(jīng)驗中提煉的,沒有經(jīng)過提煉的小白,很難駕馭這樣的知識。所以在學(xué)習(xí)的過程中首先要有案例,之后再結(jié)合案例與自己實際的業(yè)務(wù),嘗試重構(gòu)改造,慢慢體會其中的感受,從而也就學(xué)會了如果搭建出優(yōu)秀的代碼。
 
JDK 1.8 Idea + Maven 涉及工程三個,可以通過關(guān)注公眾號 :bugstack蟲洞棧源碼下載獲取 工程 描述 itstack-demo-design-2-00 場景模擬工程,模擬出使用Redis升級為集群時類改造 itstack-demo-design-2-01 使用一坨代碼實現(xiàn)業(yè)務(wù)需求,也是對ifelse的使用 itstack-demo-design-2-02 通過設(shè)計模式優(yōu)化改造代碼,產(chǎn)生對比性從而學(xué)習(xí) 
 
抽象工廠模式與工廠方法模式雖然主要意圖都是為了解決,接口選擇 問題。但在實現(xiàn)上,抽象工廠是一個中心工廠,創(chuàng)建其他工廠的模式。
 
可能在平常的業(yè)務(wù)開發(fā)中很少關(guān)注這樣的設(shè)計模式或者類似的代碼結(jié)構(gòu),但是這種場景確一直在我們身邊,例如;
 
 不同系統(tǒng)內(nèi)的回車換行
 
  Unix系統(tǒng)里,每行結(jié)尾只有 <換行> ,即 \n; Windows系統(tǒng)里面,每行結(jié)尾是 <換行><回車> ,即 \n\r; Mac系統(tǒng)里,每行結(jié)尾是 <回車>    IDEA 開發(fā)工具的差異展示(Win\Mac)
 
  除了這樣顯而易見的例子外,我們的業(yè)務(wù)開發(fā)中時常也會遇到類似的問題,需要兼容做處理 但大部分經(jīng)驗不足的開發(fā)人員,常常直接通過添加ifelse方式進(jìn)行處理了。
 
 
很多時候初期業(yè)務(wù)的蠻荒發(fā)展,也會牽動著研發(fā)對系統(tǒng)的建設(shè)。
 
預(yù)估QPS較低、系統(tǒng)壓力較小、并發(fā)訪問不大、近一年沒有大動作等等,在考慮時間投入成本的前提前,并不會投入特別多的人力去構(gòu)建非常完善的系統(tǒng)。就像對 Redis 的使用,往往可能只要是單機(jī)的就可以滿足現(xiàn)狀。
 
不吹牛的講百度首頁我上學(xué)時候一天就能寫完,等畢業(yè)工作了就算給我一年都完成不了!
 
但隨著業(yè)務(wù)超過預(yù)期的快速發(fā)展,系統(tǒng)的負(fù)載能力也要隨著跟上。原有的單機(jī) Redis 已經(jīng)滿足不了系統(tǒng)需求。這時候就需要更換為更為健壯的Redis集群服務(wù),雖然需要修改但是不能影響目前系統(tǒng)的運行,還要平滑過渡過去。
 
隨著這次的升級,可以預(yù)見的問題會有;
 
很多服務(wù)用到了Redis需要一起升級到集群。 需要兼容集群A和集群B,便于后續(xù)的災(zāi)備。 兩套集群提供的接口和方法各有差異,需要做適配。 不能影響到目前正常運行的系統(tǒng)。 itstack- demo- design- 2 - 00 
└── src
    └── main
        └── java
            └── org. itstack. demo. design
                ├── matter
                │   ├── EGM. java
                │   └── IIR. java
                └── RedisUtils. java
工程中的所有代碼可以通過關(guān)注公眾號:bugstack蟲洞棧,回復(fù)源碼下載進(jìn)行獲取。 
 
 
模擬Redis功能,也就是假定目前所有的系統(tǒng)都在使用的服務(wù) 類和方法名次都固定寫死到各個業(yè)務(wù)系統(tǒng)中,改動略微麻煩 
 
模擬一個集群服務(wù),但是方法名與各業(yè)務(wù)系統(tǒng)中使用的方法名不同。有點像你mac,我用win。做一樣的事,但有不同的操作。 
 
這是另外一套集群服務(wù),有時候在企業(yè)開發(fā)中就很有可能出現(xiàn)兩套服務(wù),這里我們也是為了做模擬案例,所以添加兩套實現(xiàn)同樣功能的不同服務(wù),來學(xué)習(xí)抽象工廠模式。 綜上可以看到,我們目前的系統(tǒng)中已經(jīng)在大量的使用redis服務(wù),但是因為系統(tǒng)不能滿足業(yè)務(wù)的快速發(fā)展,因此需要遷移到集群服務(wù)中。而這時有兩套集群服務(wù)需要兼容使用,又要滿足所有的業(yè)務(wù)系統(tǒng)改造的同時不影響線上使用。
 
以下是案例模擬中原有的單集群Redis使用方式,后續(xù)會通過對這里的代碼進(jìn)行改造。
 
 
public  interface  CacheService  { 
    String get ( final  String key) ; 
    void  set ( String key,  String value) ; 
    void  set ( String key,  String value,  long  timeout,  TimeUnit timeUnit) ; 
    void  del ( String key) ; 
} 
public  class  CacheServiceImpl  implements  CacheService  { 
    private  RedisUtils redisUtils =  new  RedisUtils ( ) ; 
    public  String get ( String key)  { 
        return  redisUtils. get ( key) ; 
    } 
    public  void  set ( String key,  String value)  { 
        redisUtils. set ( key,  value) ; 
    } 
    public  void  set ( String key,  String value,  long  timeout,  TimeUnit timeUnit)  { 
        redisUtils. set ( key,  value,  timeout,  timeUnit) ; 
    } 
    public  void  del ( String key)  { 
        redisUtils. del ( key) ; 
    } 
} 
目前的代碼對于當(dāng)前場景下的使用沒有什么問題,也比較簡單。但是所有的業(yè)務(wù)系統(tǒng)都在使用同時,需要改造就不那么容易了。這里可以思考下,看如何改造才是合理的。 講道理沒有ifelse解決不了的邏輯,不行就在加一行!
 
此時的實現(xiàn)方式并不會修改類結(jié)構(gòu)圖,也就是與上面給出的類層級關(guān)系一致。通過在接口中添加類型字段區(qū)分當(dāng)前使用的是哪個集群,來作為使用的判斷??梢哉f目前的方式非常難用,其他使用方改動頗多,這里只是做為例子。
 
itstack- demo- design- 2 - 01 
└── src
    └── main
        └── java
            └── org. itstack. demo. design
                ├── impl
                │   └── CacheServiceImpl. java
                └── CacheService. java
此時的只有兩個類,類結(jié)構(gòu)非常簡單。而我們需要的補(bǔ)充擴(kuò)展功能也只是在 CacheServiceImpl 中實現(xiàn)。 public  class  CacheServiceImpl  implements  CacheService  { 
    private  RedisUtils redisUtils =  new  RedisUtils ( ) ; 
    private  EGM egm =  new  EGM ( ) ; 
    private  IIR iir =  new  IIR ( ) ; 
    public  String get ( String key,  int  redisType)  { 
        if  ( 1  ==  redisType)  { 
            return  egm. gain ( key) ; 
        } 
        if  ( 2  ==  redisType)  { 
            return  iir. get ( key) ; 
        } 
        return  redisUtils. get ( key) ; 
    } 
    public  void  set ( String key,  String value,  int  redisType)  { 
        if  ( 1  ==  redisType)  { 
            egm. set ( key,  value) ; 
            return ; 
        } 
        if  ( 2  ==  redisType)  { 
            iir. set ( key,  value) ; 
            return ; 
        } 
        redisUtils. set ( key,  value) ; 
    } 
    //... 同類不做太多展示,可以下載源碼進(jìn)行參考 
} 
這里的實現(xiàn)過程非常簡單,主要根據(jù)類型判斷是哪個Redis集群。 雖然實現(xiàn)是簡單了,但是對使用者來說就麻煩了,并且也很難應(yīng)對后期的拓展和不停的維護(hù)。 接下來我們通過junit單元測試的方式驗證接口服務(wù),強(qiáng)調(diào)日常編寫好單測可以更好的提高系統(tǒng)的健壯度。
 
編寫測試類: 
 
@Test 
public  void  test_CacheService ( )  { 
    CacheService cacheService =  new  CacheServiceImpl ( ) ; 
    cacheService. set ( "user_name_01" ,  "小傅哥" ,  1 ) ; 
    String val01 =  cacheService. get ( "user_name_01" , 1 ) ; 
    System. out. println ( val01) ; 
} 
結(jié)果: 
 
22 : 26 : 24.591  [ main]  INFO  org. itstack. demo. design. matter. EGM -  EGM寫入數(shù)據(jù) key:user_name_01 val:小傅哥
22 : 26 : 24.593  [ main]  INFO  org. itstack. demo. design. matter. EGM -  EGM獲取數(shù)據(jù) key:user_name_01
測試結(jié)果:小傅哥
Process finished with exit code 0 
從結(jié)果上看運行正常,并沒有什么問題。但這樣的代碼只要到生成運行起來以后,想再改就真的難了! 接下來使用抽象工廠模式來進(jìn)行代碼優(yōu)化,也算是一次很小的重構(gòu)。
 
這里的抽象工廠的創(chuàng)建和獲取方式,會采用代理類的方式進(jìn)行實現(xiàn)。所被代理的類就是目前的Redis操作方法類,讓這個類在不需要任何修改下,就可以實現(xiàn)調(diào)用集群A和集群B的數(shù)據(jù)服務(wù)。
 
并且這里還有一點非常重要,由于集群A和集群B在部分方法提供上是不同的,因此需要做一個接口適配,而這個適配類就相當(dāng)于工廠中的工廠,用于創(chuàng)建把不同的服務(wù)抽象為統(tǒng)一的接口做相同的業(yè)務(wù)。這一塊與我們上一章節(jié)中的工廠方法模型類型,可以翻閱參考。
 
itstack- demo- design- 2 - 02 
└── src
    ├── main
    │   └── java
    │       └── org. itstack. demo. design
    │           ├── factory    
    │           │   ├── impl
    │           │   │   ├── EGMCacheAdapter. java 
    │           │   │   └── IIRCacheAdapter. java
    │           │   ├── ICacheAdapter. java
    │           │   ├── JDKInvocationHandler. java
    │           │   └── JDKProxy. java
    │           ├── impl
    │           │   └── CacheServiceImpl. java    
    │           └── CacheService. java 
    └── test
         └── java
             └── org. itstack. demo. design. test
                 └── ApiTest. java
抽象工廠模型結(jié)構(gòu) 
 
 
工程中涉及的部分核心功能代碼,如下; 
  ICacheAdapter,定義了適配接口,分別包裝兩個集群中差異化的接口名稱。EGMCacheAdapter、IIRCacheAdapterJDKProxy、JDKInvocationHandler,是代理類的定義和實現(xiàn),這部分也就是抽象工廠的另外一種實現(xiàn)方式。通過這樣的方式可以很好的把原有操作Redis的方法進(jìn)行代理操作,通過控制不同的入?yún)ο?#xff0c;控制緩存的使用。 好 ,那么接下來會分別講解幾個類的具體實現(xiàn)。
 
public  interface  ICacheAdapter  { 
    String get ( String key) ; 
    void  set ( String key,  String value) ; 
    void  set ( String key,  String value,  long  timeout,  TimeUnit timeUnit) ; 
    void  del ( String key) ; 
} 
這個類的主要作用是讓所有集群的提供方,能在統(tǒng)一的方法名稱下進(jìn)行操作。也方面后續(xù)的拓展。 EGMCacheAdapter 
 
public  class  EGMCacheAdapter  implements  ICacheAdapter  { 
    private  EGM egm =  new  EGM ( ) ; 
    public  String get ( String key)  { 
        return  egm. gain ( key) ; 
    } 
    public  void  set ( String key,  String value)  { 
        egm. set ( key,  value) ; 
    } 
    public  void  set ( String key,  String value,  long  timeout,  TimeUnit timeUnit)  { 
        egm. setEx ( key,  value,  timeout,  timeUnit) ; 
    } 
    public  void  del ( String key)  { 
        egm. delete ( key) ; 
    } 
} 
IIRCacheAdapter 
 
public  class  IIRCacheAdapter  implements  ICacheAdapter  { 
    private  IIR iir =  new  IIR ( ) ; 
    public  String get ( String key)  { 
        return  iir. get ( key) ; 
    } 
    public  void  set ( String key,  String value)  { 
        iir. set ( key,  value) ; 
    } 
    public  void  set ( String key,  String value,  long  timeout,  TimeUnit timeUnit)  { 
        iir. setExpire ( key,  value,  timeout,  timeUnit) ; 
    } 
    public  void  del ( String key)  { 
        iir. del ( key) ; 
    } 
} 
以上兩個實現(xiàn)都非常容易,在統(tǒng)一方法名下進(jìn)行包裝。 JDKProxy 
 
public  static  < T> getProxy ( Class< T> ,  ICacheAdapter cacheAdapter)  throws  Exception { 
    InvocationHandler handler =  new  JDKInvocationHandler ( cacheAdapter) ; 
    ClassLoader classLoader =  Thread. currentThread ( ) . getContextClassLoader ( ) ; 
    Class< ? > [ ]  classes =  interfaceClass. getInterfaces ( ) ; 
    return  ( T)  Proxy. newProxyInstance ( classLoader,  new  Class [ ] { classes[ 0 ] } ,  handler) ; 
} 
這里主要的作用就是完成代理類,同時對于使用哪個集群有外部通過入?yún)⑦M(jìn)行傳遞。 JDKInvocationHandler 
 
public  class  JDKInvocationHandler  implements  InvocationHandler  { 
    private  ICacheAdapter cacheAdapter; 
    public  JDKInvocationHandler ( ICacheAdapter cacheAdapter)  { 
        this . cacheAdapter =  cacheAdapter; 
    } 
    public  Object invoke ( Object proxy,  Method method,  Object[ ]  args)  throws  Throwable { 
        return  ICacheAdapter. class . getMethod ( method. getName ( ) ,  ClassLoaderUtils. getClazzByArgs ( args) ) . invoke ( cacheAdapter,  args) ; 
    } 
} 
在代理類的實現(xiàn)中其實也非常簡單,通過穿透進(jìn)來的集群服務(wù)進(jìn)行方法操作。 另外在invoke中通過使用獲取方法名稱反射方式,調(diào)用對應(yīng)的方法功能,也就簡化了整體的使用。 到這我們就已經(jīng)將整體的功能實現(xiàn)完成了,關(guān)于抽象工廠這部分也可以使用非代理的方式進(jìn)行實現(xiàn)。 編寫測試類: 
 
@Test 
public  void  test_CacheService ( )  throws  Exception { 
    CacheService proxy_EGM =  JDKProxy. getProxy ( CacheServiceImpl. class ,  new  EGMCacheAdapter ( ) ) ; 
    proxy_EGM. set ( "user_name_01" , "小傅哥" ) ; 
    String val01 =  proxy_EGM. get ( "user_name_01" ) ; 
    System. out. println ( val01) ; 
    
    CacheService proxy_IIR =  JDKProxy. getProxy ( CacheServiceImpl. class ,  new  IIRCacheAdapter ( ) ) ; 
    proxy_IIR. set ( "user_name_01" , "小傅哥" ) ; 
    String val02 =  proxy_IIR. get ( "user_name_01" ) ; 
    System. out. println ( val02) ; 
} 
在測試的代碼中通過傳入不同的集群類型,就可以調(diào)用不同的集群下的方法。JDKProxy.getProxy(CacheServiceImpl.class, new EGMCacheAdapter()); 如果后續(xù)有擴(kuò)展的需求,也可以按照這樣的類型方式進(jìn)行補(bǔ)充,同時對于改造上來說并沒有改動原來的方法,降低了修改成本。 結(jié)果: 
 
23 : 07 : 06.953  [ main]  INFO  org. itstack. demo. design. matter. EGM -  EGM寫入數(shù)據(jù) key:user_name_01 val:小傅哥
23 : 07 : 06.956  [ main]  INFO  org. itstack. demo. design. matter. EGM -  EGM獲取數(shù)據(jù) key:user_name_01
測試結(jié)果:小傅哥
23 : 07 : 06.957  [ main]  INFO  org. itstack. demo. design. matter. IIR -  IIR寫入數(shù)據(jù) key:user_name_01 val:小傅哥
23 : 07 : 06.957  [ main]  INFO  org. itstack. demo. design. matter. IIR -  IIR獲取數(shù)據(jù) key:user_name_01
測試結(jié)果:小傅哥
Process finished with exit code 0 
運行結(jié)果正常,這樣的代碼滿足了這次拓展的需求,同時你的技術(shù)能力也給老板留下了深刻的印象。 研發(fā)自我能力的提升遠(yuǎn)不是外接的壓力就是編寫一坨坨代碼的接口,如果你已經(jīng)熟練了很多技能,那么可以在即使緊急的情況下,也能做出完善的方案。 抽象工廠模式,所要解決的問題就是在一個產(chǎn)品族,存在多個不同類型的產(chǎn)品(Redis集群、操作系統(tǒng))情況下,接口選擇的問題。而這種場景在業(yè)務(wù)開發(fā)中也是非常多見的,只不過可能有時候沒有將它們抽象化出來。 你的代碼只是被ifelse埋上了!當(dāng)你知道什么場景下何時可以被抽象工程優(yōu)化代碼,那么你的代碼層級結(jié)構(gòu)以及滿足業(yè)務(wù)需求上,都可以得到很好的完成功能實現(xiàn)并提升擴(kuò)展性和優(yōu)雅度。那么這個設(shè)計模式滿足了;單一職責(zé)、開閉原則、解耦等優(yōu)點,但如果說隨著業(yè)務(wù)的不斷拓展,可能會造成類實現(xiàn)上的復(fù)雜度。但也可以說算不上缺點,因為可以隨著其他設(shè)計方式的引入和代理類以及自動生成加載的方式降低此項缺點。 重學(xué) Java 設(shè)計模式:實戰(zhàn)工廠方法模式Java開發(fā)架構(gòu)篇:初識領(lǐng)域驅(qū)動設(shè)計DDD落地Java開發(fā)架構(gòu)篇:DDD模型領(lǐng)域?qū)記Q策規(guī)則樹服務(wù)設(shè)計Java開發(fā)架構(gòu)篇:領(lǐng)域驅(qū)動設(shè)計架構(gòu)基于SpringCloud搭建微服務(wù)11 萬字的字節(jié)碼編程系列合集放送 
 CodeGuide | 程序員編碼指南 Go!