做者:小傅哥
博客:https://bugstack.cnhtml
沉澱、分享、成長,讓本身和他人都能有所收穫!😄
擦屁屁紙80%的面積都是保護手的!
java
工做到3年左右很大一部分程序員都想提高本身的技術棧,開始嘗試去閱讀一些源碼,例如Spring
、Mybaits
、Dubbo
等,但讀着讀着發現愈來愈難懂,一會從這過來一會跑到那去。甚至懷疑本身技術太差,慢慢也就不肯意再觸碰這部分知識。程序員
而這主要的緣由是一個框架隨着時間的發展,它的複雜程度是愈來愈高的,從最開始只有一個很是核心的點到最後開枝散葉。這就像你本身開發的業務代碼或者某個組件同樣,最開始的那部分核心代碼也許只能佔到20%,而其餘大部分代碼都是爲了保證核心流程能正常運行的。因此這也是你讀源碼費勁的一部分緣由。數據庫
框架中用到了設計模式嗎?
json
框架中不只用到設計模式還用了不少,並且有些時候根本不是一個模式的單獨使用,而是多種設計模式的綜合運用。與大部分小夥伴平時開發的CRUD可就不同了,若是都是if語句從上到下,也就算得不上什麼框架了。就像你到Spring的源碼中搜關鍵字Adapter
,就會出現不少實現類,例如;UserCredentialsDataSourceAdapter
。而這種設計模式就是咱們本文要介紹的適配器模式。設計模式
適配器在生活裏隨處可見
框架
若是提到在平常生活中就不少適配器的存在你會想到什麼?在沒有看後文以前能夠先思考下。ide
bugstack蟲洞棧
,回覆源碼下載
獲取(打開獲取的連接,找到序號18)工程 | 描述 |
---|---|
itstack-demo-design-6-00 | 場景模擬工程;模擬多個MQ消息體 |
itstack-demo-design-6-01 | 使用一坨代碼實現業務需求 |
itstack-demo-design-6-02 | 經過設計模式優化改造代碼,產生對比性從而學習 |
適配器模式的主要做用就是把本來不兼容的接口,經過適配修改作到統一。使得用戶方便使用,就像咱們提到的萬能充、數據線、MAC筆記本的轉換頭、出國旅遊買個插座等等,他們都是爲了適配各類不一樣的口
,作的兼容。。單元測試
除了咱們生活中出現的各類適配的場景,那麼在業務開發中呢?學習
在業務開發中咱們會常常的須要作不一樣接口的兼容,尤爲是中臺服務,中臺須要把各個業務線的各類類型服務作統一包裝,再對外提供接口進行使用。而這在咱們日常的開發中也是很是常見的。
隨着公司的業務的不斷髮展,當基礎的系統逐步成型之後。業務運營就須要開始作用戶的拉新和促活,從而保障DAU
的增速以及最終ROI
轉換。
而這時候就會須要作一些營銷系統,大部分常見的都是裂變、拉客,例如;你邀請一個用戶開戶、或者邀請一個用戶下單,那麼平臺就會給你返利,多邀多得。同時隨着拉新的量愈來愈多開始設置每個月下單都會給首單獎勵,等等,各類營銷場景。
那麼這個時候作這樣一個系統就會接收各類各樣的MQ消息或者接口,若是一個個的去開發,就會耗費很大的成本,同時對於後期的拓展也有必定的難度。此時就會但願有一個系統能夠配置一下就把外部的MQ接入進行,這些MQ就像上面提到的多是一些註冊開戶消息、商品下單消息等等。
而適配器的思想方式也偏偏能夠運用到這裏,而且我想強調一下,適配器不僅是能夠適配接口每每還能夠適配一些屬性信息。
itstack-demo-design-6-00 └── src └── main └── java └── org.itstack.demo.design ├── mq │ ├── create_account.java │ ├── OrderMq.java │ └── POPOrderDelivered.java └── service ├── OrderServicejava └── POPOrderService.java
public class create_account { private String number; // 開戶編號 private String address; // 開戶地 private Date accountDate; // 開戶時間 private String desc; // 開戶描述 // ... get/set }
public class OrderMq { private String uid; // 用戶ID private String sku; // 商品 private String orderId; // 訂單ID private Date createOrderTime; // 下單時間 // ... get/set }
public class POPOrderDelivered { private String uId; // 用戶ID private String orderId; // 訂單號 private Date orderTime; // 下單時間 private Date sku; // 商品 private Date skuName; // 商品名稱 private BigDecimal decimal; // 金額 // ... get/set }
public class OrderService { private Logger logger = LoggerFactory.getLogger(POPOrderService.class); public long queryUserOrderCount(String userId){ logger.info("自營商家,查詢用戶的訂單是否爲首單:{}", userId); return 10L; } }
public class POPOrderService { private Logger logger = LoggerFactory.getLogger(POPOrderService.class); public boolean isFirstOrder(String uId) { logger.info("POP商家,查詢用戶的訂單是否爲首單:{}", uId); return true; } }
其實大部分時候接MQ消息都是建立一個類用於消費,經過轉換他的MQ消息屬性給本身的方法。
咱們接下來也是先體現一下這種方式的實現模擬,可是這樣的實現有一個很大的問題就是,當MQ消息愈來愈多後,甚至幾十幾百之後,你做爲中颱要怎麼優化呢?
itstack-demo-design-6-01 └── src └── main └── java └── org.itstack.demo.design └── create_accountMqService.java └── OrderMqService.java └── POPOrderDeliveredService.java
public class create_accountMqService { public void onMessage(String message) { create_account mq = JSON.parseObject(message, create_account.class); mq.getNumber(); mq.getAccountDate(); // ... 處理本身的業務 } }
接下來使用適配器模式來進行代碼優化,也算是一次很小的重構。
適配器模式要解決的主要問題就是多種差別化類型的接口作統一輸出,這在咱們學習工廠方法模式中也有所提到不一樣種類的獎品處理,其實那也是適配器的應用。
在本文中咱們還會再另外體現出一個多種MQ接收,使用MQ的場景。來把不一樣類型的消息作統一的處理,便於減小後續對MQ接收。
在這裏若是你以前沒要開發過接收MQ消息,可能聽上去會有些不理解這樣的場景。對此,我我的建議先了解下MQ。另外就算不了解也不要緊,不會影響對思路的體會。
再者,本文所展現的MQ兼容的核心部分,也就是處理適配不一樣的類型字段。而若是咱們接收MQ後,在配置不一樣的消費類時,若是不但願一個個開發類,那麼可使用代理類的方式進行處理。
itstack-demo-design-6-02 └── src └── main └── java └── org.itstack.demo.design ├── impl │ ├── InsideOrderService.java │ └── POPOrderAdapterServiceImpl.java ├── MQAdapter,java ├── OrderAdapterService,java └── RebateInfo,java
適配器模型結構
public class RebateInfo { private String userId; // 用戶ID private String bizId; // 業務ID private Date bizTime; // 業務時間 private String desc; // 業務描述 // ... get/set }
public class MQAdapter { public static RebateInfo filter(String strJson, Map<String, String> link) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { return filter(JSON.parseObject(strJson, Map.class), link); } public static RebateInfo filter(Map obj, Map<String, String> link) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { RebateInfo rebateInfo = new RebateInfo(); for (String key : link.keySet()) { Object val = obj.get(link.get(key)); RebateInfo.class.getMethod("set" + key.substring(0, 1).toUpperCase() + key.substring(1), String.class).invoke(rebateInfo, val.toString()); } return rebateInfo; } }
用戶ID;uId
,映射到咱們須要的;userId
,作統一處理。Map<String, String> link
,也就是準確的描述了,當前MQ中某個屬性名稱,映射爲咱們的某個屬性名稱。mq
消息基本都是json
格式,能夠轉換爲MAP結構。最後使用反射調用的方式給咱們的類型賦值。@Test public void test_MQAdapter() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { create_account create_account = new create_account(); create_account.setNumber("100001"); create_account.setAddress("河北省.廊坊市.廣陽區.大學裏職業技術學院"); create_account.setAccountDate(new Date()); create_account.setDesc("在校開戶"); HashMap<String, String> link01 = new HashMap<String, String>(); link01.put("userId", "number"); link01.put("bizId", "number"); link01.put("bizTime", "accountDate"); link01.put("desc", "desc"); RebateInfo rebateInfo01 = MQAdapter.filter(create_account.toString(), link01); System.out.println("mq.create_account(適配前)" + create_account.toString()); System.out.println("mq.create_account(適配後)" + JSON.toJSONString(rebateInfo01)); System.out.println(""); OrderMq orderMq = new OrderMq(); orderMq.setUid("100001"); orderMq.setSku("10928092093111123"); orderMq.setOrderId("100000890193847111"); orderMq.setCreateOrderTime(new Date()); HashMap<String, String> link02 = new HashMap<String, String>(); link02.put("userId", "uid"); link02.put("bizId", "orderId"); link02.put("bizTime", "createOrderTime"); RebateInfo rebateInfo02 = MQAdapter.filter(orderMq.toString(), link02); System.out.println("mq.orderMq(適配前)" + orderMq.toString()); System.out.println("mq.orderMq(適配後)" + JSON.toJSONString(rebateInfo02)); }
mq.create_account(適配前){"accountDate":1591024816000,"address":"河北省.廊坊市.廣陽區.大學裏職業技術學院","desc":"在校開戶","number":"100001"} mq.create_account(適配後){"bizId":"100001","bizTime":1591077840669,"desc":"在校開戶","userId":"100001"} mq.orderMq(適配前){"createOrderTime":1591024816000,"orderId":"100000890193847111","sku":"10928092093111123","uid":"100001"} mq.orderMq(適配後){"bizId":"100000890193847111","bizTime":1591077840669,"userId":"100001"} Process finished with exit code 0
就像咱們前面提到隨着業務的發展,營銷活動自己要修改,不能只是接了MQ就發獎勵。由於此時已經拉新的愈來愈多了,須要作一些限制。
由於增長了只有首單用戶纔給獎勵,也就是你一年或者新人或者一個月的第一單纔給你獎勵,而不是你以前每一次下單都給獎勵。
那麼就須要對此種方式進行限制,而此時MQ中並無判斷首單的屬性。只能經過接口進行查詢,而拿到的接口以下;
接口 | 描述 |
---|---|
org.itstack.demo.design.service.OrderService.queryUserOrderCount(String userId) | 出參long,查詢訂單數量 |
org.itstack.demo.design.service.OrderService.POPOrderService.isFirstOrder(String uId) | 出參boolean,判斷是否首單 |
public interface OrderAdapterService { boolean isFirst(String uId); }
內部商品接口
public class InsideOrderService implements OrderAdapterService { private OrderService orderService = new OrderService(); public boolean isFirst(String uId) { return orderService.queryUserOrderCount(uId) <= 1; } }
第三方商品接口
public class POPOrderAdapterServiceImpl implements OrderAdapterService { private POPOrderService popOrderService = new POPOrderService(); public boolean isFirst(String uId) { return popOrderService.isFirstOrder(uId); } }
<= 1
,以此判斷是否爲首單。@Test public void test_itfAdapter() { OrderAdapterService popOrderAdapterService = new POPOrderAdapterServiceImpl(); System.out.println("判斷首單,接口適配(POP):" + popOrderAdapterService.isFirst("100001")); OrderAdapterService insideOrderService = new InsideOrderService(); System.out.println("判斷首單,接口適配(自營):" + insideOrderService.isFirst("100001")); }
23:25:47.076 [main] INFO o.i.d.design.service.POPOrderService - POP商家,查詢用戶的訂單是否爲首單:100001 判斷首單,接口適配(POP):true 23:25:47.079 [main] INFO o.i.d.design.service.POPOrderService - 自營商家,查詢用戶的訂單是否爲首單:100001 判斷首單,接口適配(自營):false Process finished with exit code 0