作者:小傅哥 博客:https://
沉淀、分享、成長,讓自己和他人都能有所收獲!😄
一、前言
好看的代碼千篇一律,惡心的程序升職加薪。
該說不說幾乎是程序員就都知道或者了解設(shè)計模式,但大部分小伙伴寫代碼總是習(xí)慣于一把梭。無論多少業(yè)務(wù)邏輯就一個類幾千行,這樣的開發(fā)也可以歸納為三步;定義屬性、創(chuàng)建方法、調(diào)用展示,Done!只不過開發(fā)一時爽,重構(gòu)火葬場。
好的代碼不只為了完成現(xiàn)有功能,也會考慮后續(xù)擴展。在結(jié)構(gòu)設(shè)計上松耦合易讀易擴展,在領(lǐng)域?qū)崿F(xiàn)上高內(nèi)聚不對外暴漏實現(xiàn)細節(jié)不被外部干擾。而這就有點像家里三居(MVC)室、四居(DDD)室的裝修,你不會允許幾十萬的房子把走線水管裸漏在外面,也不會允許把馬桶放到廚房,爐灶安裝到衛(wèi)生間。
誰發(fā)明了設(shè)計模式? 設(shè)計模式的概念最早是由 克里斯托佛·亞歷山大 在其著作 《建筑模式語言》 中首次提出的。 本書介紹了城市設(shè)計的 “語言”,提供了253個描述城鎮(zhèn)、鄰里、住宅、花園、房間及西部構(gòu)造的模式, 而此類 “語言” 的基本單元就是模式。后來,埃里希·伽瑪、 約翰·弗利賽德斯、 拉爾夫·約翰遜 和 理查德·赫爾姆 這四位作者接受了模式的概念。 1994 年, 他們出版了 《設(shè)計模式: 可復(fù)用面向?qū)ο筌浖幕A(chǔ)》 一書, 將設(shè)計模式的概念應(yīng)用到程序開發(fā)領(lǐng)域中。
其實有一部分人并沒有仔細閱讀過設(shè)計模式的相關(guān)書籍和資料,但依舊可以編寫出優(yōu)秀的代碼。這主要是由于在經(jīng)過眾多項目的錘煉和對程序設(shè)計的不斷追求,從而在多年編程歷程上提煉出來的心得體會。而這份經(jīng)驗最終會與設(shè)計模式提到的內(nèi)容幾乎一致,同樣會要求高內(nèi)聚、低耦合、可擴展、可復(fù)用。你可能也遇到類似的經(jīng)歷,在學(xué)習(xí)一些框架的源碼時,發(fā)現(xiàn)它里的某些設(shè)計和你在做開發(fā)時一樣。
我怎么學(xué)不會設(shè)計模式? 錢也花了,書也買了。代碼還是一坨一坨的!設(shè)計模式是由多年的經(jīng)驗提煉出來開發(fā)指導(dǎo)思想。就像我告訴你自行車怎么騎、汽車怎么開,但只要你沒跑過幾千公里,你能記住的只是理論,想上道依舊很慌!
所以 ,本設(shè)計模式專題系列開始,會帶著你使用設(shè)計模式的思想去優(yōu)化代碼。從而學(xué)習(xí)設(shè)計模式的心得并融入給自己。當然這里還需要多加練習(xí),一定是人車合一 ,才能站在設(shè)計模式的基礎(chǔ)上構(gòu)建出更加合理的代碼。
二、開發(fā)環(huán)境
JDK 1.8
Idea + Maven
涉及工程三個,可以通過關(guān)注公眾號 :bugstack蟲洞棧,回復(fù)源碼下載獲取。你會獲得一個連接打開后的列表中編號18:itstack-demo-design
工程 描述 itstack-demo-design-1-00 場景模擬工程,用于提供三組不同獎品的發(fā)放接口 itstack-demo-design-1-01 使用一坨代碼實現(xiàn)業(yè)務(wù)需求,也是對ifelse的使用 itstack-demo-design-1-02 通過設(shè)計模式優(yōu)化改造代碼,產(chǎn)生對比性從而學(xué)習(xí)
1-00,1 代表著第一個設(shè)計模式,工廠方法模式 1-00,00 代表模擬的場景 1-01,01 代表第一種實現(xiàn)方案,后續(xù) 02 03 以此類推
二、工廠方法模式介紹
工廠方法模式,圖片來自 refactoringguru.cn
工廠模式又稱工廠方法模式,是一種創(chuàng)建型設(shè)計模式,其在父類中提供一個創(chuàng)建對象的方法, 允許子類決定實例化對象的類型。
這種設(shè)計模式也是 Java 開發(fā)中最常見的一種模式,它的主要意圖是定義一個創(chuàng)建對象的接口,讓其子類自己決定實例化哪一個工廠類,工廠模式使其創(chuàng)建過程延遲到子類進行。
簡單說就是為了提供代碼結(jié)構(gòu)的擴展性,屏蔽每一個功能類中的具體實現(xiàn)邏輯。讓外部可以更加簡單的只是知道調(diào)用即可,同時,這也是去掉眾多ifeslse的方式。當然這可能也有一些缺點,比如需要實現(xiàn)的類非常多,如何去維護,怎樣減低開發(fā)成本。但這些問題都可以在后續(xù)的設(shè)計模式結(jié)合使用中,逐步降低。
三、模擬發(fā)獎多種商品
為了可以讓整個學(xué)習(xí)的案例更加貼近實際開發(fā),這里模擬互聯(lián)網(wǎng)中在營銷場景下的業(yè)務(wù)。由于營銷場景的復(fù)雜、多變、臨時的特性,它所需要的設(shè)計需要更加深入,否則會經(jīng)常面臨各種緊急CRUD操作,從而讓代碼結(jié)構(gòu)混亂不堪,難以維護。
在營銷場景中經(jīng)常會有某個用戶做了一些操作;打卡、分享、留言、邀請注冊等等,進行返利積分,最后通過積分在兌換商品,從而促活和拉新。
那么在這里我們模擬積分兌換中的發(fā)放多種類型商品,假如現(xiàn)在我們有如下三種類型的商品接口;
序號 類型 接口 1 優(yōu)惠券 CouponResult sendCoupon(String uId, String couponNumber, String uuid)2 實物商品 Boolean deliverGoods(DeliverReq req)3 第三方愛奇藝兌換卡 void grantToken(String bindMobileNumber, String cardId)
從以上接口來看有如下信息:
三個接口返回類型不同,有對象類型、布爾類型、還有一個空類型。 入?yún)⒉煌?#xff0c;發(fā)放優(yōu)惠券需要仿重、兌換卡需要卡ID、實物商品需要發(fā)貨位置(對象中含有)。 另外可能會隨著后續(xù)的業(yè)務(wù)的發(fā)展,會新增其他種商品類型。因為你所有的開發(fā)需求都是隨著業(yè)務(wù)對市場的拓展而帶來的。
四、用一坨坨代碼實現(xiàn)
如果不考慮任何擴展性,只為了盡快滿足需求,那么對這么幾種獎勵發(fā)放只需使用ifelse語句判斷,調(diào)用不同的接口即可滿足需求??赡苓@也是一些剛?cè)腴T編程的小伙伴,常用的方式。接下來我們就先按照這樣的方式來實現(xiàn)業(yè)務(wù)的需求。
1. 工程結(jié)構(gòu)
itstack- demo- design- 1 - 01
└── src
├── main
│ └── java
│ └── org. itstack. demo. design
│ ├── AwardReq. java
│ ├── AwardRes. java
│ └── PrizeController. java
└── test
└── java
└── org. itstack. demo. design. test
└── ApiTest. java
工程結(jié)構(gòu)上非常簡單,一個入?yún)ο?AwardReq 、一個出參對象 AwardRes,以及一個接口類 PrizeController
2. ifelse實現(xiàn)需求
public class PrizeController {
private Logger logger = LoggerFactory. getLogger ( PrizeController. class ) ;
public AwardRes awardToUser ( AwardReq req) {
String reqJson = JSON. toJSONString ( req) ;
AwardRes awardRes = null;
try {
logger. info ( "獎品發(fā)放開始{}。req:{}" , req. getuId ( ) , reqJson) ;
// 按照不同類型方法商品[1優(yōu)惠券、2實物商品、3第三方兌換卡(愛奇藝)]
if ( req. getAwardType ( ) == 1 ) {
CouponService couponService = new CouponService ( ) ;
CouponResult couponResult = couponService. sendCoupon ( req. getuId ( ) , req. getAwardNumber ( ) , req. getBizId ( ) ) ;
if ( "0000" . equals ( couponResult. getCode ( ) ) ) {
awardRes = new AwardRes ( "0000" , "發(fā)放成功" ) ;
} else {
awardRes = new AwardRes ( "0001" , couponResult. getInfo ( ) ) ;
}
} else if ( req. getAwardType ( ) == 2 ) {
GoodsService goodsService = new GoodsService ( ) ;
DeliverReq deliverReq = new DeliverReq ( ) ;
deliverReq. setUserName ( queryUserName ( req. getuId ( ) ) ) ;
deliverReq. setUserPhone ( queryUserPhoneNumber ( req. getuId ( ) ) ) ;
deliverReq. setSku ( req. getAwardNumber ( ) ) ;
deliverReq. setOrderId ( req. getBizId ( ) ) ;
deliverReq. setConsigneeUserName ( req. getExtMap ( ) . get ( "consigneeUserName" ) ) ;
deliverReq. setConsigneeUserPhone ( req. getExtMap ( ) . get ( "consigneeUserPhone" ) ) ;
deliverReq. setConsigneeUserAddress ( req. getExtMap ( ) . get ( "consigneeUserAddress" ) ) ;
Boolean isSuccess = goodsService. deliverGoods ( deliverReq) ;
if ( isSuccess) {
awardRes = new AwardRes ( "0000" , "發(fā)放成功" ) ;
} else {
awardRes = new AwardRes ( "0001" , "發(fā)放失敗" ) ;
}
} else if ( req. getAwardType ( ) == 3 ) {
String bindMobileNumber = queryUserPhoneNumber ( req. getuId ( ) ) ;
IQiYiCardService iQiYiCardService = new IQiYiCardService ( ) ;
iQiYiCardService. grantToken ( bindMobileNumber, req. getAwardNumber ( ) ) ;
awardRes = new AwardRes ( "0000" , "發(fā)放成功" ) ;
}
logger. info ( "獎品發(fā)放完成{}。" , req. getuId ( ) ) ;
} catch ( Exception e) {
logger. error ( "獎品發(fā)放失敗{}。req:{}" , req. getuId ( ) , reqJson, e) ;
awardRes = new AwardRes ( "0001" , e. getMessage ( ) ) ;
}
return awardRes;
}
private String queryUserName ( String uId) {
return "花花" ;
}
private String queryUserPhoneNumber ( String uId) {
return "15200101232" ;
}
}
如上就是使用 ifelse 非常直接的實現(xiàn)出來業(yè)務(wù)需求的一坨代碼,如果僅從業(yè)務(wù)角度看,研發(fā)如期甚至提前實現(xiàn)了功能。 那這樣的代碼目前來看并不會有什么問題,但如果在經(jīng)過幾次的迭代和拓展,接手這段代碼的研發(fā)將十分痛苦。重構(gòu)成本高需要理清之前每一個接口的使用,測試回歸驗證時間長,需要全部驗證一次。這也就是很多人并不愿意接手別人的代碼,如果接手了又被壓榨開發(fā)時間。那么可想而知這樣的 ifelse 還會繼續(xù)增加。
3. 測試驗證
寫一個單元測試來驗證上面編寫的接口方式,養(yǎng)成單元測試的好習(xí)慣會為你增強代碼質(zhì)量。
編寫測試類:
@Test
public void test_awardToUser ( ) {
PrizeController prizeController = new PrizeController ( ) ;
System. out. println ( "\r\n模擬發(fā)放優(yōu)惠券測試\r\n" ) ;
// 模擬發(fā)放優(yōu)惠券測試
AwardReq req01 = new AwardReq ( ) ;
req01. setuId ( "10001" ) ;
req01. setAwardType ( 1 ) ;
req01. setAwardNumber ( "EGM1023938910232121323432" ) ;
req01. setBizId ( "791098764902132" ) ;
AwardRes awardRes01 = prizeController. awardToUser ( req01) ;
logger. info ( "請求參數(shù):{}" , JSON. toJSON ( req01) ) ;
logger. info ( "測試結(jié)果:{}" , JSON. toJSON ( awardRes01) ) ;
System. out. println ( "\r\n模擬方法實物商品\r\n" ) ;
// 模擬方法實物商品
AwardReq req02 = new AwardReq ( ) ;
req02. setuId ( "10001" ) ;
req02. setAwardType ( 2 ) ;
req02. setAwardNumber ( "9820198721311" ) ;
req02. setBizId ( "1023000020112221113" ) ;
Map< String, String> extMap = new HashMap < String, String> ( ) ;
extMap. put ( "consigneeUserName" , "謝飛機" ) ;
extMap. put ( "consigneeUserPhone" , "15200292123" ) ;
extMap. put ( "consigneeUserAddress" , "吉林省.長春市.雙陽區(qū).XX街道.檀溪苑小區(qū).#18-2109" ) ;
req02. setExtMap ( extMap) ;
commodityService_2. sendCommodity ( "10001" , "9820198721311" , "1023000020112221113" , extMap) ;
AwardRes awardRes02 = prizeController. awardToUser ( req02) ;
logger. info ( "請求參數(shù):{}" , JSON. toJSON ( req02) ) ;
logger. info ( "測試結(jié)果:{}" , JSON. toJSON ( awardRes02) ) ;
System. out. println ( "\r\n第三方兌換卡(愛奇藝)\r\n" ) ;
AwardReq req03 = new AwardReq ( ) ;
req03. setuId ( "10001" ) ;
req03. setAwardType ( 3 ) ;
req03. setAwardNumber ( "AQY1xjkUodl8LO975GdfrYUio" ) ;
AwardRes awardRes03 = prizeController. awardToUser ( req03) ;
logger. info ( "請求參數(shù):{}" , JSON. toJSON ( req03) ) ;
logger. info ( "測試結(jié)果:{}" , JSON. toJSON ( awardRes03) ) ;
}
結(jié)果:
模擬發(fā)放優(yōu)惠券測試
22 : 17 : 55.668 [ main] INFO o. i. demo. design. PrizeController - 獎品發(fā)放開始10001 。req: { "awardNumber" : "EGM1023938910232121323432" , "awardType" : 1 , "bizId" : "791098764902132" , "uId" : "10001" }
模擬發(fā)放優(yōu)惠券一張:10001 , EGM1023938910232121323432, 791098764902132
22 : 17 : 55.671 [ main] INFO o. i. demo. design. PrizeController - 獎品發(fā)放完成10001 。
22 : 17 : 55.673 [ main] INFO org. itstack. demo. test. ApiTest - 請求參數(shù):{ "uId" : "10001" , "bizId" : "791098764902132" , "awardNumber" : "EGM1023938910232121323432" , "awardType" : 1 }
22 : 17 : 55.674 [ main] INFO org. itstack. demo. test. ApiTest - 測試結(jié)果:{ "code" : "0000" , "info" : "發(fā)放成功" }
模擬方法實物商品
22 : 17 : 55.675 [ main] INFO o. i. demo. design. PrizeController - 獎品發(fā)放開始10001 。req: { "awardNumber" : "9820198721311" , "awardType" : 2 , "bizId" : "1023000020112221113" , "extMap" : { "consigneeUserName" : "謝飛機" , "consigneeUserPhone" : "15200292123" , "consigneeUserAddress" : "吉林省.長春市.雙陽區(qū).XX街道.檀溪苑小區(qū).#18-2109" } , "uId" : "10001" }
模擬發(fā)貨實物商品一個:{ "consigneeUserAddress" : "吉林省.長春市.雙陽區(qū).XX街道.檀溪苑小區(qū).#18-2109" , "consigneeUserName" : "謝飛機" , "consigneeUserPhone" : "15200292123" , "orderId" : "1023000020112221113" , "sku" : "9820198721311" , "userName" : "花花" , "userPhone" : "15200101232" }
22 : 17 : 55.677 [ main] INFO o. i. demo. design. PrizeController - 獎品發(fā)放完成10001 。
22 : 17 : 55.677 [ main] INFO org. itstack. demo. test. ApiTest - 請求參數(shù):{ "extMap" : { "consigneeUserName" : "謝飛機" , "consigneeUserAddress" : "吉林省.長春市.雙陽區(qū).XX街道.檀溪苑小區(qū).#18-2109" , "consigneeUserPhone" : "15200292123" } , "uId" : "10001" , "bizId" : "1023000020112221113" , "awardNumber" : "9820198721311" , "awardType" : 2 }
22 : 17 : 55.677 [ main] INFO org. itstack. demo. test. ApiTest - 測試結(jié)果:{ "code" : "0000" , "info" : "發(fā)放成功" }
第三方兌換卡( 愛奇藝)
22 : 17 : 55.678 [ main] INFO o. i. demo. design. PrizeController - 獎品發(fā)放開始10001 。req: { "awardNumber" : "AQY1xjkUodl8LO975GdfrYUio" , "awardType" : 3 , "uId" : "10001" }
模擬發(fā)放愛奇藝會員卡一張:15200101232 ,AQY1xjkUodl8LO975GdfrYUio
22 : 17 : 55.678 [ main] INFO o. i. demo. design. PrizeController - 獎品發(fā)放完成10001 。
22 : 17 : 55.678 [ main] INFO org. itstack. demo. test. ApiTest - 請求參數(shù):{ "uId" : "10001" , "awardNumber" : "AQY1xjkUodl8LO975GdfrYUio" , "awardType" : 3 }
22 : 17 : 55.678 [ main] INFO org. itstack. demo. test. ApiTest - 測試結(jié)果:{ "code" : "0000" , "info" : "發(fā)放成功" }
Process finished with exit code 0
運行結(jié)果正常,滿足當前所有業(yè)務(wù)產(chǎn)品需求,寫的還很快。但!實在難以為維護!
五、工廠模式優(yōu)化代碼
接下來使用工廠方法模式來進行代碼優(yōu)化,也算是一次很小的重構(gòu) 。整理重構(gòu)會你會發(fā)現(xiàn)代碼結(jié)構(gòu)清晰了、也具備了下次新增業(yè)務(wù)需求的擴展性。但在實際使用中還會對此進行完善,目前的只是抽離出最核心的部分體現(xiàn)到你面前,方便學(xué)習(xí)。
1. 工程結(jié)構(gòu)
itstack- demo- design- 1 - 02
└── src
├── main
│ └── java
│ └── org. itstack. demo. design
│ ├── store
│ │ ├── impl
│ │ │ ├── CardCommodityService. java
│ │ │ ├── CouponCommodityService. java
│ │ │ └── GoodsCommodityService. java
│ │ └── ICommodity. java
│ └── StoreFactory. java
└── test
└── java
└── org. itstack. demo. design. test
└── ApiTest. java
首先,從上面的工程結(jié)構(gòu)中你是否一些感覺,比如;它看上去清晰了、這樣分層可以更好擴展了、似乎可以想象到每一個類做了什么。 如果還不能理解為什么這樣修改,也沒有關(guān)系。因為你是在通過這樣的文章,來學(xué)習(xí)設(shè)計模式的魅力。并且再獲取源碼后,進行實際操作幾次也就慢慢掌握了工廠模式的技巧。
2. 代碼實現(xiàn)
2.1 定義發(fā)獎接口
public interface ICommodity {
void sendCommodity ( String uId, String commodityId, String bizId, Map< String, String> extMap) throws Exception;
}
所有的獎品無論是實物、虛擬還是第三方,都需要通過我們的程序?qū)崿F(xiàn)此接口進行處理,以保證最終入?yún)⒊鰠⒌慕y(tǒng)一性。 接口的入?yún)?#xff1b;用戶ID、獎品ID、業(yè)務(wù)ID以及擴展字段用于處理發(fā)放實物商品時的收獲地址。
2.2 實現(xiàn)獎品發(fā)放接口
優(yōu)惠券
public class CouponCommodityService implements ICommodity {
private Logger logger = LoggerFactory. getLogger ( CouponCommodityService. class ) ;
private CouponService couponService = new CouponService ( ) ;
public void sendCommodity ( String uId, String commodityId, String bizId, Map< String, String> extMap) throws Exception {
CouponResult couponResult = couponService. sendCoupon ( uId, commodityId, bizId) ;
logger. info ( "請求參數(shù)[優(yōu)惠券] => uId:{} commodityId:{} bizId:{} extMap:{}" , uId, commodityId, bizId, JSON. toJSON ( extMap) ) ;
logger. info ( "測試結(jié)果[優(yōu)惠券]:{}" , JSON. toJSON ( couponResult) ) ;
if ( ! "0000" . equals ( couponResult. getCode ( ) ) ) throw new RuntimeException ( couponResult. getInfo ( ) ) ;
}
}
實物商品
public class GoodsCommodityService implements ICommodity {
private Logger logger = LoggerFactory. getLogger ( GoodsCommodityService. class ) ;
private GoodsService goodsService = new GoodsService ( ) ;
public void sendCommodity ( String uId, String commodityId, String bizId, Map< String, String> extMap) throws Exception {
DeliverReq deliverReq = new DeliverReq ( ) ;
deliverReq. setUserName ( queryUserName ( uId) ) ;
deliverReq. setUserPhone ( queryUserPhoneNumber ( uId) ) ;
deliverReq. setSku ( commodityId) ;
deliverReq. setOrderId ( bizId) ;
deliverReq. setConsigneeUserName ( extMap. get ( "consigneeUserName" ) ) ;
deliverReq. setConsigneeUserPhone ( extMap. get ( "consigneeUserPhone" ) ) ;
deliverReq. setConsigneeUserAddress ( extMap. get ( "consigneeUserAddress" ) ) ;
Boolean isSuccess = goodsService. deliverGoods ( deliverReq) ;
logger. info ( "請求參數(shù)[優(yōu)惠券] => uId:{} commodityId:{} bizId:{} extMap:{}" , uId, commodityId, bizId, JSON. toJSON ( extMap) ) ;
logger. info ( "測試結(jié)果[優(yōu)惠券]:{}" , isSuccess) ;
if ( ! isSuccess) throw new RuntimeException ( "實物商品發(fā)放失敗" ) ;
}
private String queryUserName ( String uId) {
return "花花" ;
}
private String queryUserPhoneNumber ( String uId) {
return "15200101232" ;
}
}
第三方兌換卡
public class CardCommodityService implements ICommodity {
private Logger logger = LoggerFactory. getLogger ( CardCommodityService. class ) ;
// 模擬注入
private IQiYiCardService iQiYiCardService = new IQiYiCardService ( ) ;
public void sendCommodity ( String uId, String commodityId, String bizId, Map< String, String> extMap) throws Exception {
String mobile = queryUserMobile ( uId) ;
iQiYiCardService. grantToken ( mobile, bizId) ;
logger. info ( "請求參數(shù)[愛奇藝兌換卡] => uId:{} commodityId:{} bizId:{} extMap:{}" , uId, commodityId, bizId, JSON. toJSON ( extMap) ) ;
logger. info ( "測試結(jié)果[愛奇藝兌換卡]:success" ) ;
}
private String queryUserMobile ( String uId) {
return "15200101232" ;
}
}
從上面可以看到每一種獎品的實現(xiàn)都包括在自己的類中,新增、修改或者刪除都不會影響其他獎品功能的測試,降低回歸測試的可能。 后續(xù)在新增的獎品只需要按照此結(jié)構(gòu)進行填充即可,非常易于維護和擴展。 在統(tǒng)一了入?yún)⒁约俺鰠⒑?#xff0c;調(diào)用方不在需要關(guān)心獎品發(fā)放的內(nèi)部邏輯,按照統(tǒng)一的方式即可處理。
2.3 創(chuàng)建商店工廠
public class StoreFactory {
public ICommodity getCommodityService ( Integer commodityType) {
if ( null == commodityType) return null;
if ( 1 == commodityType) return new CouponCommodityService ( ) ;
if ( 2 == commodityType) return new GoodsCommodityService ( ) ;
if ( 3 == commodityType) return new CardCommodityService ( ) ;
throw new RuntimeException ( "不存在的商品服務(wù)類型" ) ;
}
}
這里我們定義了一個商店的工廠類,在里面按照類型實現(xiàn)各種商品的服務(wù)??梢苑浅8蓛粽麧嵉奶幚砟愕拇a,后續(xù)新增的商品在這里擴展即可。如果你不喜歡if判斷,也可以使用switch或者map配置結(jié)構(gòu),會讓代碼更加干凈。 另外很多代碼檢查軟件和編碼要求,不喜歡if語句后面不寫擴展,這里是為了更加干凈的向你體現(xiàn)邏輯。在實際的業(yè)務(wù)編碼中可以添加括號。
3. 測試驗證
編寫測試類:
@Test
public void test_commodity ( ) throws Exception {
StoreFactory storeFactory = new StoreFactory ( ) ;
// 1. 優(yōu)惠券
ICommodity commodityService_1 = storeFactory. getCommodityService ( 1 ) ;
commodityService_1. sendCommodity ( "10001" , "EGM1023938910232121323432" , "791098764902132" , null) ;
// 2. 實物商品
ICommodity commodityService_2 = storeFactory. getCommodityService ( 2 ) ;
Map< String, String> extMap = new HashMap < String, String> ( ) ;
extMap. put ( "consigneeUserName" , "謝飛機" ) ;
extMap. put ( "consigneeUserPhone" , "15200292123" ) ;
extMap. put ( "consigneeUserAddress" , "吉林省.長春市.雙陽區(qū).XX街道.檀溪苑小區(qū).#18-2109" ) ;
commodityService_2. sendCommodity ( "10001" , "9820198721311" , "1023000020112221113" , extMap) ;
// 3. 第三方兌換卡(愛奇藝)
ICommodity commodityService_3 = storeFactory. getCommodityService ( 3 ) ;
commodityService_3. sendCommodity ( "10001" , "AQY1xjkUodl8LO975GdfrYUio" , null, null) ;
}
結(jié)果:
模擬發(fā)放優(yōu)惠券一張:10001 , EGM1023938910232121323432, 791098764902132
22 : 48 : 10.922 [ main] INFO o. i. d. d. s. i. CouponCommodityService - 請求參數(shù)[ 優(yōu)惠券] = > uId:10001 commodityId:EGM1023938910232121323432 bizId:791098764902132 extMap:null
22 : 48 : 10.957 [ main] INFO o. i. d. d. s. i. CouponCommodityService - 測試結(jié)果[ 優(yōu)惠券] :{ "code" : "0000" , "info" : "發(fā)放成功" }
模擬發(fā)貨實物商品一個:{ "consigneeUserAddress" : "吉林省.長春市.雙陽區(qū).XX街道.檀溪苑小區(qū).#18-2109" , "consigneeUserName" : "謝飛機" , "consigneeUserPhone" : "15200292123" , "orderId" : "1023000020112221113" , "sku" : "9820198721311" , "userName" : "花花" , "userPhone" : "15200101232" }
22 : 48 : 10.962 [ main] INFO o. i. d. d. s. impl. GoodsCommodityService - 請求參數(shù)[ 優(yōu)惠券] = > uId:10001 commodityId:9820198721311 bizId:1023000020112221113 extMap:{ "consigneeUserName" : "謝飛機" , "consigneeUserAddress" : "吉林省.長春市.雙陽區(qū).XX街道.檀溪苑小區(qū).#18-2109" , "consigneeUserPhone" : "15200292123" }
22 : 48 : 10.962 [ main] INFO o. i. d. d. s. impl. GoodsCommodityService - 測試結(jié)果[ 優(yōu)惠券] :true
模擬發(fā)放愛奇藝會員卡一張:15200101232 ,null
22 : 48 : 10.963 [ main] INFO o. i. d. d. s. impl. CardCommodityService - 請求參數(shù)[ 愛奇藝兌換卡] = > uId:10001 commodityId:AQY1xjkUodl8LO975GdfrYUio bizId:null extMap:null
22 : 48 : 10.963 [ main] INFO o. i. d. d. s. impl. CardCommodityService - 測試結(jié)果[ 愛奇藝兌換卡] :success
Process finished with exit code 0
運行結(jié)果正常,既滿足了業(yè)務(wù)產(chǎn)品需求,也滿足了自己對代碼的追求。這樣的代碼部署上線運行,內(nèi)心不會恐慌,不會覺得半夜會有電話。 另外從運行測試結(jié)果上也可以看出來,在進行封裝后可以非常清晰的看到一整套發(fā)放獎品服務(wù)的完整性,統(tǒng)一了入?yún)?、統(tǒng)一了結(jié)果。
六、總結(jié)
從上到下的優(yōu)化來看,工廠方法模式并不復(fù)雜,甚至這樣的開發(fā)結(jié)構(gòu)在你有所理解后,會發(fā)現(xiàn)更加簡單了。 那么這樣的開發(fā)的好處知道后,也可以總結(jié)出來它的優(yōu)點;避免創(chuàng)建者與具體的產(chǎn)品邏輯耦合、滿足單一職責(zé),每一個業(yè)務(wù)邏輯實現(xiàn)都在所屬自己的類中完成、滿足開閉原則,無需更改使用調(diào)用方就可以在程序中引入新的產(chǎn)品類型。但這樣也會帶來一些問題,比如有非常多的獎品類型,那么實現(xiàn)的子類會極速擴張。因此也需要使用其他的模式進行優(yōu)化,這些在后續(xù)的設(shè)計模式中會逐步涉及到。 從案例入手看設(shè)計模式往往要比看理論學(xué)的更加容易,因為案例是縮短理論到上手的最佳方式,如果你已經(jīng)有所收獲,一定要去嘗試實操。
七、往期推薦
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! 本代碼庫是作者小傅哥多年從事一線互聯(lián)網(wǎng) Java 開發(fā)的學(xué)習(xí)歷程技術(shù)匯總,旨在為大家提供一個清晰詳細的學(xué)習(xí)教程,側(cè)重點更傾向編寫Java核心內(nèi)容。如果本倉庫能為您提供幫助,請給予支持(關(guān)注、點贊、分享)!