做者:小傅哥
博客:https://bugstack.cnhtml
沉澱、分享、成長,讓本身和他人都能有所收穫!😄
好看的代碼千篇一概,噁心的程序升職加薪。
java
該說不說幾乎是程序員就都知道或者瞭解設計模式,但大部分小夥伴寫代碼老是習慣於一把梭。不管多少業務邏輯就一個類幾千行,這樣的開發也能夠概括爲三步;定義屬性、建立方法、調用展現,Done!只不過開發一時爽,重構火葬場。git
好的代碼不僅爲了完成現有功能,也會考慮後續擴展。在結構設計上鬆耦合易讀易擴展,在領域實現上高內聚不對外暴漏實現細節不被外部干擾。而這就有點像家裏三居(MVC)室、四居(DDD)室的裝修,你不會容許幾十萬的房子把走線水管裸漏在外面,也不會容許把馬桶放到廚房,爐竈安裝到衛生間。程序員
誰發明了設計模式? 設計模式的概念最先是由 克里斯托佛·亞歷山大
在其著做 《建築模式語言》
中首次提出的。 本書介紹了城市設計的 「語言」,提供了253個描述城鎮、鄰里、住宅、花園、房間及西部構造的模式, 而此類 「語言」 的基本單元就是模式。後來,埃裏希·伽瑪
、 約翰·弗利賽德斯
、 拉爾夫·約翰遜
和 理查德·赫爾姆
這四位做者接受了模式的概念。 1994 年, 他們出版了 《設計模式: 可複用面向對象軟件的基礎》
一書, 將設計模式的概念應用到程序開發領域中。 github
其實有一部分人並無仔細閱讀過設計模式的相關書籍和資料,但依舊能夠編寫出優秀的代碼。這主要是因爲在通過衆多項目的錘鍊和對程序設計的不斷追求,從而在多年編程歷程上提煉出來的心得體會。而這份經驗最終會與設計模式提到的內容幾乎一致,一樣會要求高內聚、低耦合、可擴展、可複用。你可能也遇到相似的經歷,在學習一些框架的源碼時,發現它裏的某些設計和你在作開發時同樣。編程
我怎麼學不會設計模式? 錢也花了,書也買了。代碼仍是一坨一坨的!設計模式是由多年的經驗提煉出來開發指導思想。就像我告訴你自行車怎麼騎、汽車怎麼開,但只要你沒跑過幾千千米,你能記住的只是理論,想上道依舊很慌!設計模式
因此,本設計模式專題系列開始,會帶着你使用設計模式的思想去優化代碼。從而學習設計模式的心得並融入給本身。固然這裏還須要多加練習,必定是人車合一,才能站在設計模式的基礎上構建出更加合理的代碼。架構
涉及工程三個,能夠經過關注公衆號:bugstack蟲洞棧
,回覆源碼下載
獲取。你會得到一個鏈接打開後的列表中編號18
:itstack-demo-design
框架
工程 | 描述 |
---|---|
itstack-demo-design-1-00 | 場景模擬工程,用於提供三組不一樣獎品的發放接口 |
itstack-demo-design-1-01 | 使用一坨代碼實現業務需求,也是對ifelse的使用 |
itstack-demo-design-1-02 | 經過設計模式優化改造代碼,產生對比性從而學習 |
工廠模式又稱工廠方法模式,是一種建立型設計模式,其在父類中提供一個建立對象的方法, 容許子類決定實例化對象的類型。ide
這種設計模式也是 Java 開發中最多見的一種模式,它的主要意圖是定義一個建立對象的接口,讓其子類本身決定實例化哪個工廠類,工廠模式使其建立過程延遲到子類進行。
簡單說就是爲了提供代碼結構的擴展性,屏蔽每個功能類中的具體實現邏輯。讓外部能夠更加簡單的只是知道調用便可,同時,這也是去掉衆多ifeslse
的方式。固然這可能也有一些缺點,好比須要實現的類很是多,如何去維護,怎樣減低開發成本。但這些問題均可以在後續的設計模式結合使用中,逐步下降。
爲了可讓整個學習的案例更加貼近實際開發,這裏模擬互聯網中在營銷場景下的業務。因爲營銷場景的複雜、多變、臨時的特性,它所須要的設計須要更加深刻,不然會常常面臨各類緊急CRUD操做,從而讓代碼結構混亂不堪,難以維護。
在營銷場景中常常會有某個用戶作了一些操做;打卡、分享、留言、邀請註冊等等,進行返利積分,最後經過積分在兌換商品,從而促活和拉新。
那麼在這裏咱們模擬積分兌換中的發放多種類型商品,假如如今咱們有以下三種類型的商品接口;
序號 | 類型 | 接口 |
---|---|---|
1 | 優惠券 | CouponResult sendCoupon(String uId, String couponNumber, String uuid) |
2 | 實物商品 | Boolean deliverGoods(DeliverReq req) |
3 | 第三方愛奇藝兌換卡 | void grantToken(String bindMobileNumber, String cardId) |
從以上接口來看有以下信息:
若是不考慮任何擴展性,只爲了儘快知足需求,那麼對這麼幾種獎勵發放只需使用ifelse語句判斷,調用不一樣的接口便可知足需求。可能這也是一些剛入門編程的小夥伴,經常使用的方式。接下來咱們就先按照這樣的方式來實現業務的需求。
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
AwardReq
、一個出參對象 AwardRes
,以及一個接口類 PrizeController
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("獎品發放開始{}。req:{}", req.getuId(), reqJson); // 按照不一樣類型方法商品[1優惠券、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", "發放成功"); } 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", "發放成功"); } else { awardRes = new AwardRes("0001", "發放失敗"); } } else if (req.getAwardType() == 3) { String bindMobileNumber = queryUserPhoneNumber(req.getuId()); IQiYiCardService iQiYiCardService = new IQiYiCardService(); iQiYiCardService.grantToken(bindMobileNumber, req.getAwardNumber()); awardRes = new AwardRes("0000", "發放成功"); } logger.info("獎品發放完成{}。", req.getuId()); } catch (Exception e) { logger.error("獎品發放失敗{}。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
很是直接的實現出來業務需求的一坨代碼,若是僅從業務角度看,研發如期甚至提早實現了功能。ifelse
還會繼續增長。寫一個單元測試來驗證上面編寫的接口方式,養成單元測試的好習慣會爲你加強代碼質量。
編寫測試類:
@Test public void test_awardToUser() { PrizeController prizeController = new PrizeController(); System.out.println("\r\n模擬發放優惠券測試\r\n"); // 模擬發放優惠券測試 AwardReq req01 = new AwardReq(); req01.setuId("10001"); req01.setAwardType(1); req01.setAwardNumber("EGM1023938910232121323432"); req01.setBizId("791098764902132"); AwardRes awardRes01 = prizeController.awardToUser(req01); logger.info("請求參數:{}", JSON.toJSON(req01)); logger.info("測試結果:{}", 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", "吉林省.長春市.雙陽區.XX街道.檀溪苑小區.#18-2109"); req02.setExtMap(extMap); commodityService_2.sendCommodity("10001","9820198721311","1023000020112221113", extMap); AwardRes awardRes02 = prizeController.awardToUser(req02); logger.info("請求參數:{}", JSON.toJSON(req02)); logger.info("測試結果:{}", 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("請求參數:{}", JSON.toJSON(req03)); logger.info("測試結果:{}", JSON.toJSON(awardRes03)); }
結果:
模擬發放優惠券測試 22:17:55.668 [main] INFO o.i.demo.design.PrizeController - 獎品發放開始10001。req:{"awardNumber":"EGM1023938910232121323432","awardType":1,"bizId":"791098764902132","uId":"10001"} 模擬發放優惠券一張:10001,EGM1023938910232121323432,791098764902132 22:17:55.671 [main] INFO o.i.demo.design.PrizeController - 獎品發放完成10001。 22:17:55.673 [main] INFO org.itstack.demo.test.ApiTest - 請求參數:{"uId":"10001","bizId":"791098764902132","awardNumber":"EGM1023938910232121323432","awardType":1} 22:17:55.674 [main] INFO org.itstack.demo.test.ApiTest - 測試結果:{"code":"0000","info":"發放成功"} 模擬方法實物商品 22:17:55.675 [main] INFO o.i.demo.design.PrizeController - 獎品發放開始10001。req:{"awardNumber":"9820198721311","awardType":2,"bizId":"1023000020112221113","extMap":{"consigneeUserName":"謝飛機","consigneeUserPhone":"15200292123","consigneeUserAddress":"吉林省.長春市.雙陽區.XX街道.檀溪苑小區.#18-2109"},"uId":"10001"} 模擬發貨實物商品一個:{"consigneeUserAddress":"吉林省.長春市.雙陽區.XX街道.檀溪苑小區.#18-2109","consigneeUserName":"謝飛機","consigneeUserPhone":"15200292123","orderId":"1023000020112221113","sku":"9820198721311","userName":"花花","userPhone":"15200101232"} 22:17:55.677 [main] INFO o.i.demo.design.PrizeController - 獎品發放完成10001。 22:17:55.677 [main] INFO org.itstack.demo.test.ApiTest - 請求參數:{"extMap":{"consigneeUserName":"謝飛機","consigneeUserAddress":"吉林省.長春市.雙陽區.XX街道.檀溪苑小區.#18-2109","consigneeUserPhone":"15200292123"},"uId":"10001","bizId":"1023000020112221113","awardNumber":"9820198721311","awardType":2} 22:17:55.677 [main] INFO org.itstack.demo.test.ApiTest - 測試結果:{"code":"0000","info":"發放成功"} 第三方兌換卡(愛奇藝) 22:17:55.678 [main] INFO o.i.demo.design.PrizeController - 獎品發放開始10001。req:{"awardNumber":"AQY1xjkUodl8LO975GdfrYUio","awardType":3,"uId":"10001"} 模擬發放愛奇藝會員卡一張:15200101232,AQY1xjkUodl8LO975GdfrYUio 22:17:55.678 [main] INFO o.i.demo.design.PrizeController - 獎品發放完成10001。 22:17:55.678 [main] INFO org.itstack.demo.test.ApiTest - 請求參數:{"uId":"10001","awardNumber":"AQY1xjkUodl8LO975GdfrYUio","awardType":3} 22:17:55.678 [main] INFO org.itstack.demo.test.ApiTest - 測試結果:{"code":"0000","info":"發放成功"} Process finished with exit code 0
接下來使用工廠方法模式來進行代碼優化,也算是一次很小的重構。整理重構會你會發現代碼結構清晰了、也具有了下次新增業務需求的擴展性。但在實際使用中還會對此進行完善,目前的只是抽離出最核心的部分體現到你面前,方便學習。
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
工廠模式
的技巧。public interface ICommodity { void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception; }
用戶ID
、獎品ID
、業務ID
以及擴展字段
用於處理髮放實物商品時的收穫地址。優惠券
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("請求參數[優惠券] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap)); logger.info("測試結果[優惠券]:{}", 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("請求參數[優惠券] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap)); logger.info("測試結果[優惠券]:{}", isSuccess); if (!isSuccess) throw new RuntimeException("實物商品發放失敗"); } 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("請求參數[愛奇藝兌換卡] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap)); logger.info("測試結果[愛奇藝兌換卡]:success"); } private String queryUserMobile(String uId) { return "15200101232"; } }
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("不存在的商品服務類型"); } }
if
判斷,也可使用switch
或者map
配置結構,會讓代碼更加乾淨。編寫測試類:
@Test public void test_commodity() throws Exception { StoreFactory storeFactory = new StoreFactory(); // 1. 優惠券 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", "吉林省.長春市.雙陽區.XX街道.檀溪苑小區.#18-2109"); commodityService_2.sendCommodity("10001","9820198721311","1023000020112221113", extMap); // 3. 第三方兌換卡(愛奇藝) ICommodity commodityService_3 = storeFactory.getCommodityService(3); commodityService_3.sendCommodity("10001","AQY1xjkUodl8LO975GdfrYUio",null,null); }
結果:
模擬發放優惠券一張:10001,EGM1023938910232121323432,791098764902132 22:48:10.922 [main] INFO o.i.d.d.s.i.CouponCommodityService - 請求參數[優惠券] => uId:10001 commodityId:EGM1023938910232121323432 bizId:791098764902132 extMap:null 22:48:10.957 [main] INFO o.i.d.d.s.i.CouponCommodityService - 測試結果[優惠券]:{"code":"0000","info":"發放成功"} 模擬發貨實物商品一個:{"consigneeUserAddress":"吉林省.長春市.雙陽區.XX街道.檀溪苑小區.#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 - 請求參數[優惠券] => uId:10001 commodityId:9820198721311 bizId:1023000020112221113 extMap:{"consigneeUserName":"謝飛機","consigneeUserAddress":"吉林省.長春市.雙陽區.XX街道.檀溪苑小區.#18-2109","consigneeUserPhone":"15200292123"} 22:48:10.962 [main] INFO o.i.d.d.s.impl.GoodsCommodityService - 測試結果[優惠券]:true 模擬發放愛奇藝會員卡一張:15200101232,null 22:48:10.963 [main] INFO o.i.d.d.s.impl.CardCommodityService - 請求參數[愛奇藝兌換卡] => uId:10001 commodityId:AQY1xjkUodl8LO975GdfrYUio bizId:null extMap:null 22:48:10.963 [main] INFO o.i.d.d.s.impl.CardCommodityService - 測試結果[愛奇藝兌換卡]:success Process finished with exit code 0
避免建立者與具體的產品邏輯耦合
、知足單一職責,每個業務邏輯實現都在所屬本身的類中完成
、知足開閉原則,無需更改使用調用方就能夠在程序中引入新的產品類型
。但這樣也會帶來一些問題,好比有很是多的獎品類型,那麼實現的子類會極速擴張。所以也須要使用其餘的模式進行優化,這些在後續的設計模式中會逐步涉及到。Java開發架構篇:初識領域驅動設計DDD落地
Java開發架構篇:DDD模型領域層決策規則樹服務設計
Java開發架構篇:領域驅動設計架構基於SpringCloud搭建微服務
11 萬字的字節碼編程系列合集放送
CodeGuide | 程序員編碼指南 Go!
<br/>本代碼庫是做者小傅哥多年從事一線互聯網 Java 開發的學習歷程技術彙總,旨在爲你們提供一個清晰詳細的學習教程,側重點更傾向編寫Java核心內容。若是本倉庫能爲您提供幫助,請給予支持(關注、點贊、分享)!