詳解Java實(shí)踐之抽象工廠模式
代碼一把梭,兄弟來(lái)背鍋。
大部分做開發(fā)的小伙伴初心都希望把代碼寫好,除了把編程當(dāng)作工作以外他們還是具備工匠精神的從業(yè)者。但很多時(shí)候又很難讓你把初心堅(jiān)持下去,就像;接了個(gè)爛手的項(xiàng)目、產(chǎn)品功能要的急、個(gè)人能力不足,等等原因?qū)е鹿こ檀a臃腫不堪,線上頻出事故,最終離職走人。
看了很多書、學(xué)了很多知識(shí),多線程能玩出花,可最后我還是寫不好代碼!
這就有點(diǎn)像家里裝修完了買物件,我?guī)资f(wàn)的實(shí)木沙發(fā),怎么放這里就不好看。同樣代碼寫的不好并不一定是基礎(chǔ)技術(shù)不足,也不一定是產(chǎn)品要得急 怎么實(shí)現(xiàn)我不管明天上線。而很多時(shí)候是我們對(duì)編碼的經(jīng)驗(yàn)的不足和對(duì)架構(gòu)的把控能力不到位,我相信產(chǎn)品的第一個(gè)需求往往都不復(fù)雜,甚至所見所得。但如果你不考慮后續(xù)的是否會(huì)拓展,將來(lái)會(huì)在哪些模塊繼續(xù)添加功能,那么后續(xù)的代碼就會(huì)隨著你種下的第一顆惡性的種子開始蔓延。
學(xué)習(xí)設(shè)計(jì)模式的心得有哪些,怎么學(xué)才會(huì)用!
設(shè)計(jì)模式書籍,有點(diǎn)像考駕駛證的科一、家里裝修時(shí)的手冊(cè)、或者單身狗的戀愛寶典。但!你只要不實(shí)操,一定能搞的亂碼七糟。因?yàn)檫@些指導(dǎo)思想都是從實(shí)際經(jīng)驗(yàn)中提煉的,沒有經(jīng)過(guò)提煉的小白,很難駕馭這樣的知識(shí)。所以在學(xué)習(xí)的過(guò)程中首先要有案例,之后再結(jié)合案例與自己實(shí)際的業(yè)務(wù),嘗試重構(gòu)改造,慢慢體會(huì)其中的感受,從而也就學(xué)會(huì)了如果搭建出優(yōu)秀的代碼。
二、開發(fā)環(huán)境JDK 1.8
Idea + Maven
工程 描述 itstack-demo-design-2-00 場(chǎng)景模擬工程,模擬出使用Redis升級(jí)為集群時(shí)類改造 itstack-demo-design-2-01 使用一坨代碼實(shí)現(xiàn)業(yè)務(wù)需求,也是對(duì)ifelse的使用 itstack-demo-design-2-02 通過(guò)設(shè)計(jì)模式優(yōu)化改造代碼,產(chǎn)生對(duì)比性從而學(xué)習(xí) 三、抽象工廠模式介紹抽象工廠模式與工廠方法模式雖然主要意圖都是為了解決,接口選擇問(wèn)題。但在實(shí)現(xiàn)上,抽象工廠是一個(gè)中心工廠,創(chuàng)建其他工廠的模式。
可能在平常的業(yè)務(wù)開發(fā)中很少關(guān)注這樣的設(shè)計(jì)模式或者類似的代碼結(jié)構(gòu),但是這種場(chǎng)景確一直在我們身邊,例如;
1.不同系統(tǒng)內(nèi)的回車換行
Unix系統(tǒng)里,每行結(jié)尾只有 <換行>,即 n; Windows系統(tǒng)里面,每行結(jié)尾是 <換行><回車>,即 nr; Mac系統(tǒng)里,每行結(jié)尾是 <回車>2.IDEA 開發(fā)工具的差異展示(WinMac)
除了這樣顯而易見的例子外,我們的業(yè)務(wù)開發(fā)中時(shí)常也會(huì)遇到類似的問(wèn)題,需要兼容做處理但大部分經(jīng)驗(yàn)不足的開發(fā)人員,常常直接通過(guò)添加ifelse方式進(jìn)行處理了。
四、案例場(chǎng)景模擬很多時(shí)候初期業(yè)務(wù)的蠻荒發(fā)展,也會(huì)牽動(dòng)著研發(fā)對(duì)系統(tǒng)的建設(shè)。
預(yù)估QPS較低、系統(tǒng)壓力較小、并發(fā)訪問(wèn)不大、近一年沒有大動(dòng)作等等,在考慮時(shí)間投入成本的前提前,并不會(huì)投入特別多的人力去構(gòu)建非常完善的系統(tǒng)。就像對(duì) Redis 的使用,往往可能只要是單機(jī)的就可以滿足現(xiàn)狀。
不吹牛的講百度首頁(yè)我上學(xué)時(shí)候一天就能寫完,等畢業(yè)工作了就算給我一年都完成不了!
但隨著業(yè)務(wù)超過(guò)預(yù)期的快速發(fā)展,系統(tǒng)的負(fù)載能力也要隨著跟上。原有的單機(jī) Redis 已經(jīng)滿足不了系統(tǒng)需求。這時(shí)候就需要更換為更為健壯的Redis集群服務(wù),雖然需要修改但是不能影響目前系統(tǒng)的運(yùn)行,還要平滑過(guò)渡過(guò)去。
隨著這次的升級(jí),可以預(yù)見的問(wèn)題會(huì)有;
很多服務(wù)用到了Redis需要一起升級(jí)到集群。 需要兼容集群A和集群B,便于后續(xù)的災(zāi)備。 兩套集群提供的接口和方法各有差異,需要做適配。 不能影響到目前正常運(yùn)行的系統(tǒng)。4.1、場(chǎng)景模擬工程itstack-demo-design-2-00
└── src
└── main
└── java
└── org.itstack.demo.design
├── matter
│ ├── EGM.java
│ └── IIR.java
└── RedisUtils.java
4.2、場(chǎng)景簡(jiǎn)述4.2.1、模擬單機(jī)服務(wù) RedisUtils模擬一個(gè)集群服務(wù),但是方法名與各業(yè)務(wù)系統(tǒng)中使用的方法名不同。有點(diǎn)像你mac,我用win。做一樣的事,但有不同的操作。
4.2.3、模擬集群 IIR這是另外一套集群服務(wù),有時(shí)候在企業(yè)開發(fā)中就很有可能出現(xiàn)兩套服務(wù),這里我們也是為了做模擬案例,所以添加兩套實(shí)現(xiàn)同樣功能的不同服務(wù),來(lái)學(xué)習(xí)抽象工廠模式。
綜上可以看到,我們目前的系統(tǒng)中已經(jīng)在大量的使用redis服務(wù),但是因?yàn)橄到y(tǒng)不能滿足業(yè)務(wù)的快速發(fā)展,因此需要遷移到集群服務(wù)中。而這時(shí)有兩套集群服務(wù)需要兼容使用,又要滿足所有的業(yè)務(wù)系統(tǒng)改造的同時(shí)不影響線上使用。
4.3、單集群代碼使用以下是案例模擬中原有的單集群Redis使用方式,后續(xù)會(huì)通過(guò)對(duì)這里的代碼進(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);}4.3.2、實(shí)現(xiàn)調(diào)用代碼
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); }}
目前的代碼對(duì)于當(dāng)前場(chǎng)景下的使用沒有什么問(wèn)題,也比較簡(jiǎn)單。但是所有的業(yè)務(wù)系統(tǒng)都在使用同時(shí),需要改造就不那么容易了。這里可以思考下,看如何改造才是合理的。
五、代碼實(shí)現(xiàn)講道理沒有ifelse解決不了的邏輯,不行就在加一行!
此時(shí)的實(shí)現(xiàn)方式并不會(huì)修改類結(jié)構(gòu)圖,也就是與上面給出的類層級(jí)關(guān)系一致。通過(guò)在接口中添加類型字段區(qū)分當(dāng)前使用的是哪個(gè)集群,來(lái)作為使用的判斷。可以說(shuō)目前的方式非常難用,其他使用方改動(dòng)頗多,這里只是做為例子。
5.1、工程結(jié)構(gòu)itstack-demo-design-2-01
└── src
└── main
└── java
└── org.itstack.demo.design
├── impl
│ └── CacheServiceImpl.java
└── CacheService.java
此時(shí)的只有兩個(gè)類,類結(jié)構(gòu)非常簡(jiǎn)單。而我們需要的補(bǔ)充擴(kuò)展功能也只是在 CacheServiceImpl 中實(shí)現(xiàn)。
5.2、ifelse實(shí)現(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); }} 這里的實(shí)現(xiàn)過(guò)程非常簡(jiǎn)單,主要根據(jù)類型判斷是哪個(gè)Redis集群。 雖然實(shí)現(xiàn)是簡(jiǎn)單了,但是對(duì)使用者來(lái)說(shuō)就麻煩了,并且也很難應(yīng)對(duì)后期的拓展和不停的維護(hù)。5.3、測(cè)試驗(yàn)證
接下來(lái)我們通過(guò)junit單元測(cè)試的方式驗(yàn)證接口服務(wù),強(qiáng)調(diào)日常編寫好單測(cè)可以更好的提高系統(tǒng)的健壯度。
編寫測(cè)試類:
@Testpublic 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
測(cè)試結(jié)果:小傅哥
Process finished with exit code 0
從結(jié)果上看運(yùn)行正常,并沒有什么問(wèn)題。但這樣的代碼只要到生成運(yùn)行起來(lái)以后,想再改就真的難了!
六、抽象工廠模式重構(gòu)代碼接下來(lái)使用抽象工廠模式來(lái)進(jìn)行代碼優(yōu)化,也算是一次很小的重構(gòu)。
這里的抽象工廠的創(chuàng)建和獲取方式,會(huì)采用代理類的方式進(jìn)行實(shí)現(xiàn)。所被代理的類就是目前的Redis操作方法類,讓這個(gè)類在不需要任何修改下,就可以實(shí)現(xiàn)調(diào)用集群A和集群B的數(shù)據(jù)服務(wù)。
并且這里還有一點(diǎn)非常重要,由于集群A和集群B在部分方法提供上是不同的,因此需要做一個(gè)接口適配,而這個(gè)適配類就相當(dāng)于工廠中的工廠,用于創(chuàng)建把不同的服務(wù)抽象為統(tǒng)一的接口做相同的業(yè)務(wù)。這一塊與我們上一章節(jié)中的工廠方法模型類型,可以翻閱參考。
6.1、工程結(jié)構(gòu)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,定義了適配接口,分別包裝兩個(gè)集群中差異化的接口名稱。EGMCacheAdapter、IIRCacheAdapter JDKProxy、JDKInvocationHandler,是代理類的定義和實(shí)現(xiàn),這部分也就是抽象工廠的另外一種實(shí)現(xiàn)方式。通過(guò)這樣的方式可以很好的把原有操作Redis的方法進(jìn)行代理操作,通過(guò)控制不同的入?yún)?duì)象,控制緩存的使用。好,那么接下來(lái)會(huì)分別講解幾個(gè)類的具體實(shí)現(xiàn)。
6.2、代碼實(shí)現(xiàn)6.2.1、定義適配接口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);}
這個(gè)類的主要作用是讓所有集群的提供方,能在統(tǒng)一的方法名稱下進(jìn)行操作。也方面后續(xù)的拓展。
6.2.2、實(shí)現(xiàn)集群使用服務(wù)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); }}
以上兩個(gè)實(shí)現(xiàn)都非常容易,在統(tǒng)一方法名下進(jìn)行包裝。
6.2.3、定義抽象工程代理類和實(shí)現(xiàn)JDKProxy
public static <T> T getProxy(Class<T> interfaceClass, 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);}
這里主要的作用就是完成代理類,同時(shí)對(duì)于使用哪個(gè)集群有外部通過(guò)入?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); }} 在代理類的實(shí)現(xiàn)中其實(shí)也非常簡(jiǎn)單,通過(guò)穿透進(jìn)來(lái)的集群服務(wù)進(jìn)行方法操作。 另外在invoke中通過(guò)使用獲取方法名稱反射方式,調(diào)用對(duì)應(yīng)的方法功能,也就簡(jiǎn)化了整體的使用。 到這我們就已經(jīng)將整體的功能實(shí)現(xiàn)完成了,關(guān)于抽象工廠這部分也可以使用非代理的方式進(jìn)行實(shí)現(xiàn)。6.3、測(cè)試驗(yàn)證
編寫測(cè)試類:
@Testpublic 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);} 在測(cè)試的代碼中通過(guò)傳入不同的集群類型,就可以調(diào)用不同的集群下的方法。JDKProxy.getProxy(CacheServiceImpl.class, new EGMCacheAdapter()); 如果后續(xù)有擴(kuò)展的需求,也可以按照這樣的類型方式進(jìn)行補(bǔ)充,同時(shí)對(duì)于改造上來(lái)說(shuō)并沒有改動(dòng)原來(lái)的方法,降低了修改成本。
結(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
測(cè)試結(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
測(cè)試結(jié)果:小傅哥
Process finished with exit code 0
運(yùn)行結(jié)果正常,這樣的代碼滿足了這次拓展的需求,同時(shí)你的技術(shù)能力也給老板留下了深刻的印象。研發(fā)自我能力的提升遠(yuǎn)不是外接的壓力就是編寫一坨坨代碼的接口,如果你已經(jīng)熟練了很多技能,那么可以在即使緊急的情況下,也能做出完善的方案。
七、總結(jié)抽象工廠模式,所要解決的問(wèn)題就是在一個(gè)產(chǎn)品族,存在多個(gè)不同類型的產(chǎn)品(Redis集群、操作系統(tǒng))情況下,接口選擇的問(wèn)題。而這種場(chǎng)景在業(yè)務(wù)開發(fā)中也是非常多見的,只不過(guò)可能有時(shí)候沒有將它們抽象化出來(lái)。
你的代碼只是被ifelse埋上了!當(dāng)你知道什么場(chǎng)景下何時(shí)可以被抽象工程優(yōu)化代碼,那么你的代碼層級(jí)結(jié)構(gòu)以及滿足業(yè)務(wù)需求上,都可以得到很好的完成功能實(shí)現(xiàn)并提升擴(kuò)展性和優(yōu)雅度。
那么這個(gè)設(shè)計(jì)模式滿足了;單一職責(zé)、開閉原則、解耦等優(yōu)點(diǎn),但如果說(shuō)隨著業(yè)務(wù)的不斷拓展,可能會(huì)造成類實(shí)現(xiàn)上的復(fù)雜度。但也可以說(shuō)算不上缺點(diǎn),因?yàn)榭梢噪S著其他設(shè)計(jì)方式的引入和代理類以及自動(dòng)生成加載的方式降低此項(xiàng)缺點(diǎn)。
以上就是詳解Java實(shí)踐之抽象工廠模式的詳細(xì)內(nèi)容,更多關(guān)于Java抽象工廠模式的資料請(qǐng)關(guān)注好吧啦網(wǎng)其它相關(guān)文章!
相關(guān)文章:
1. python爬蟲實(shí)戰(zhàn)之制作屬于自己的一個(gè)IP代理模塊2. 基于javaweb+jsp實(shí)現(xiàn)企業(yè)財(cái)務(wù)記賬管理系統(tǒng)3. 詳解盒子端CSS動(dòng)畫性能提升4. HTML 絕對(duì)路徑與相對(duì)路徑概念詳細(xì)5. css代碼優(yōu)化的12個(gè)技巧6. 使用FormData進(jìn)行Ajax請(qǐng)求上傳文件的實(shí)例代碼7. 如何在jsp界面中插入圖片8. .NET6打包部署到Windows Service的全過(guò)程9. asp批量添加修改刪除操作示例代碼10. 解決ajax請(qǐng)求后臺(tái),有時(shí)收不到返回值的問(wèn)題
