做者:小傅哥
博客:https://bugstack.cn - 原創系列專題文章
html
沉澱、分享、成長,讓本身和他人都能有所收穫!😄
知道的越多不知道的就越多
java
編程開發這條路上的知識是無窮無盡的,就像之前你敢說精通Java,到後來學到愈來愈多隻想寫了解Java,過了幾年如今可能想說懂一點點Java。當視野和格局的擴大,會讓咱們愈來愈發現原來的見解是多麼淺顯,這就像站在地球看地球和站在宇宙看地球同樣。但正由於胸懷和眼界的提高讓咱們有了更多的認識,也逐漸學會了更多的技能。雖然不知道的愈來愈多,但也所以給本身填充了更多的技術棧,讓本身愈來愈強大。算法
拒絕學習的惰性很可怕
編程
如今與之前不同,資料多、途徑廣,在這中間夾雜的廣告也很是多。這就讓不少初學者很難找到本身要的知識,最後看到有人推薦相關學習資料馬上屏蔽、刪除,但同時技術優秀的資料也不能讓須要的人看見了。長此以往把更多的時間精力都放在遊戲、娛樂、影音上,適當的放鬆是能夠的,但每每沉迷之後就很難出來,所以須要作好一些可讓本身成長的計劃,稍有剋制。設計模式
平衡好軟件設計和實現成本的度°
微信
有時候一個軟件的架構設計須要符合當前條件下的各項因素,每每不能由於心中想固然的有某個藍圖,就去開始執行。也許雖然你的設計是很是優秀的,可是放在當前環境下很難知足業務的時間要求,當一個業務的基本訴求不能知足後,就很難拉動市場。沒有產品的DAU支撐,最後整個研發的項目也會所以停滯。但研發又不能一團亂麻的寫代碼,所以須要找好一個適合的度,好比能夠搭建良好的地基,實現上可擴展。但在具體的功能上能夠先簡化實現,隨着活下來了再繼續完善迭代。架構
bugstack蟲洞棧
,回覆源碼下載
獲取(打開獲取的連接,找到序號18)工程 | 描述 |
---|---|
itstack-demo-design-18-00 | 場景模擬工程;模擬一個小客車搖號接口 |
itstack-demo-design-18-01 | 使用一坨代碼實現業務需求 |
itstack-demo-design-18-02 | 經過設計模式優化改造代碼,產生對比性從而學習 |
簡單來說觀察者🕵模式,就是當一個行爲發生時傳遞信息給另一個用戶接收作出相應的處理,二者之間沒有直接的耦合關聯。例如;狙擊手、李雲龍。異步
除了生活中的場景外,在咱們編程開發中也會經常使用到一些觀察者的模式或者組件,例如咱們常用的MQ服務,雖然MQ服務是有一個通知中心並非每個類服務進行通知,但總體上也能夠算做是觀察者模式的思路設計。再好比可能有作過的一些相似事件監聽總線,讓主線服務與其餘輔線業務服務分離,爲了使系統下降耦合和加強擴展性,也會使用觀察者模式進行處理。ide
在本案例中咱們模擬每次小客車指標搖號事件通知場景(真實的不會由官網給你發消息)函數
可能大部分人看到這個案例必定會想到本身每次搖號都不中的場景,收到一個遺憾的短信通知。固然目前的搖號系統並不會給你發短信,而是由百度或者一些其餘插件發的短信。那麼假如這個相似的搖號功能若是由你來開發,而且須要對外部的用戶作一些事件通知以及須要在主流程外再添加一些額外的輔助流程時該如何處理呢?
基本不少人對於這樣的通知事件類的實現每每比較粗獷,直接在類裏面就添加了。1是考慮🤔這可能不會怎麼擴展,2是壓根就沒考慮😄過。但若是你有仔細思考過你的核心類功能會發現,這裏面有一些核心主鏈路,還有一部分是輔助功能。好比完成了某個行爲後須要觸發MQ給外部,以及作一些消息PUSH給用戶等,這些都不算作是核心流程鏈路,是能夠經過事件通知的方式進行處理。
那麼接下來咱們就使用這樣的設計模式來優化重構此場景下的代碼。
itstack-demo-design-18-00 └── src └── main └── java └── org.itstack.demo.design └── MinibusTargetService.java
public class MinibusTargetService { /** * 模擬搖號,但不是搖號算法 * * @param uId 用戶編號 * @return 結果 */ public String lottery(String uId) { return Math.abs(uId.hashCode()) % 2 == 0 ? "恭喜你,編碼".concat(uId).concat("在本次搖號中籤") : "很遺憾,編碼".concat(uId).concat("在本次搖號未中籤或搖號資格已過時"); } }
這裏咱們先使用最粗暴的方式來實現功能
按照需求須要在原有的搖號接口中添加MQ消息發送以及短消息通知功能,若是是最直接的方式那麼能夠直接在方法中補充功能便可。
itstack-demo-design-18-01 └── src └── main └── java └── org.itstack.demo.design ├── LotteryResult.java ├── LotteryService.java └── LotteryServiceImpl.java
LotteryResult
)、定義接口(LotteryService
)、具體實現(LotteryServiceImpl
)。public class LotteryServiceImpl implements LotteryService { private Logger logger = LoggerFactory.getLogger(LotteryServiceImpl.class); private MinibusTargetService minibusTargetService = new MinibusTargetService(); public LotteryResult doDraw(String uId) { // 搖號 String lottery = minibusTargetService.lottery(uId); // 發短信 logger.info("給用戶 {} 發送短信通知(短信):{}", uId, lottery); // 發MQ消息 logger.info("記錄用戶 {} 搖號結果(MQ):{}", uId, lottery); // 結果 return new LotteryResult(uId, lottery, new Date()); } }
搖號
接口調用外,後面的兩部分都是非核心主鏈路功能,並且會隨着後續的業務需求發展而不斷的調整和擴充,在這樣的開發方式下就很是不利於維護。@Test public void test() { LotteryService lotteryService = new LotteryServiceImpl(); LotteryResult result = lotteryService.doDraw("2765789109876"); logger.info("測試結果:{}", JSON.toJSONString(result)); }
22:02:24.520 [main] INFO o.i.demo.design.LotteryServiceImpl - 給用戶 2765789109876 發送短信通知(短信):很遺憾,編碼2765789109876在本次搖號未中籤或搖號資格已過時 22:02:24.523 [main] INFO o.i.demo.design.LotteryServiceImpl - 記錄用戶 2765789109876 搖號結果(MQ):很遺憾,編碼2765789109876在本次搖號未中籤或搖號資格已過時 22:02:24.606 [main] INFO org.itstack.demo.design.ApiTest - 測試結果:{"dateTime":1598764144524,"msg":"很遺憾,編碼2765789109876在本次搖號未中籤或搖號資格已過時","uId":"2765789109876"} Process finished with exit code 0
接下來使用觀察者模式來進行代碼優化,也算是一次很小的重構。
itstack-demo-design-18-02 └── src └── main └── java └── org.itstack.demo.design ├── event │ ├── listener │ │ ├── EventListener.java │ │ ├── MessageEventListener.java │ │ └── MQEventListener.java │ └── EventManager.java ├── LotteryResult.java ├── LotteryService.java └── LotteryServiceImpl.java
觀察者模式模型結構
事件監聽
、事件處理
、具體的業務流程
,另外在業務流程中 LotteryService
定義的是抽象類,由於這樣能夠經過抽象類將事件功能屏蔽,外部業務流程開發者不須要知道具體的通知操做。public interface EventListener { void doEvent(LotteryResult result); }
<T>
短消息事件
public class MessageEventListener implements EventListener { private Logger logger = LoggerFactory.getLogger(MessageEventListener.class); @Override public void doEvent(LotteryResult result) { logger.info("給用戶 {} 發送短信通知(短信):{}", result.getuId(), result.getMsg()); } }
MQ發送事件
public class MQEventListener implements EventListener { private Logger logger = LoggerFactory.getLogger(MQEventListener.class); @Override public void doEvent(LotteryResult result) { logger.info("記錄用戶 {} 搖號結果(MQ):{}", result.getuId(), result.getMsg()); } }
public class EventManager { Map<Enum<EventType>, List<EventListener>> listeners = new HashMap<>(); public EventManager(Enum<EventType>... operations) { for (Enum<EventType> operation : operations) { this.listeners.put(operation, new ArrayList<>()); } } public enum EventType { MQ, Message } /** * 訂閱 * @param eventType 事件類型 * @param listener 監聽 */ public void subscribe(Enum<EventType> eventType, EventListener listener) { List<EventListener> users = listeners.get(eventType); users.add(listener); } /** * 取消訂閱 * @param eventType 事件類型 * @param listener 監聽 */ public void unsubscribe(Enum<EventType> eventType, EventListener listener) { List<EventListener> users = listeners.get(eventType); users.remove(listener); } /** * 通知 * @param eventType 事件類型 * @param result 結果 */ public void notify(Enum<EventType> eventType, LotteryResult result) { List<EventListener> users = listeners.get(eventType); for (EventListener listener : users) { listener.doEvent(result); } } }
subscribe
)、取消訂閱(unsubscribe
)、通知(notify
)。這三個方法分別用於對監聽時間的添加和使用。EventType.MQ
、EventType.Message
)。public abstract class LotteryService { private EventManager eventManager; public LotteryService() { eventManager = new EventManager(EventManager.EventType.MQ, EventManager.EventType.Message); eventManager.subscribe(EventManager.EventType.MQ, new MQEventListener()); eventManager.subscribe(EventManager.EventType.Message, new MessageEventListener()); } public LotteryResult draw(String uId) { LotteryResult lotteryResult = doDraw(uId); // 須要什麼通知就給調用什麼方法 eventManager.notify(EventManager.EventType.MQ, lotteryResult); eventManager.notify(EventManager.EventType.Message, lotteryResult); return lotteryResult; } protected abstract LotteryResult doDraw(String uId); }
abstract LotteryResult doDraw(String uId)
,讓類的繼承者實現。protected
,也就是保證未來外部的調用方不會調用到此方法,只有調用到draw(String uId)
,才能讓咱們完成事件通知。eventManager.subscribe(EventManager.EventType.MQ, new MQEventListener())
。EventManager.EventType.MQ
,就會執行什麼事件通知,按需添加。public class LotteryServiceImpl extends LotteryService { private MinibusTargetService minibusTargetService = new MinibusTargetService(); @Override protected LotteryResult doDraw(String uId) { // 搖號 String lottery = minibusTargetService.lottery(uId); // 結果 return new LotteryResult(uId, lottery, new Date()); } }
@Test public void test() { LotteryService lotteryService = new LotteryServiceImpl(); LotteryResult result = lotteryService.draw("2765789109876"); logger.info("測試結果:{}", JSON.toJSONString(result)); }
23:56:07.597 [main] INFO o.i.d.d.e.listener.MQEventListener - 記錄用戶 2765789109876 搖號結果(MQ):很遺憾,編碼2765789109876在本次搖號未中籤或搖號資格已過時 23:56:07.600 [main] INFO o.i.d.d.e.l.MessageEventListener - 給用戶 2765789109876 發送短信通知(短信):很遺憾,編碼2765789109876在本次搖號未中籤或搖號資格已過時 23:56:07.698 [main] INFO org.itstack.demo.design.test.ApiTest - 測試結果:{"dateTime":1599737367591,"msg":"很遺憾,編碼2765789109876在本次搖號未中籤或搖號資格已過時","uId":"2765789109876"} Process finished with exit code 0
營銷
、裂變
、促活
等等,所以使用設計模式架設代碼就顯得很是有必要。1. 重學 Java 設計模式:實戰工廠方法模式「多種類型商品不一樣接口,統一發獎服務搭建場景」
2. 重學 Java 設計模式:實戰原型模式「上機考試多套試,每人題目和答案亂序排列場景」
3. 重學 Java 設計模式:實戰橋接模式「多支付渠道(微信、支付寶)與多支付模式(刷臉、指紋)場景」
4. 重學 Java 設計模式:實戰組合模式「營銷差別化人羣發券,決策樹引擎搭建場景」
5. 重學 Java 設計模式:實戰外觀模式「基於SpringBoot開發門面模式中間件,統一控制接口白名單場景」
6. 重學 Java 設計模式:實戰享元模式「基於Redis秒殺,提供活動與庫存信息查詢場景」
7. 重學 Java 設計模式:實戰備忘錄模式「模擬互聯網系統上線過程當中,配置文件回滾場景」