阿丙華為面試:什么是責任鏈模式?
前言
面試經歷大家肯定都有過,但是面試的流程其實跟一種設計模式很像,每一輪的面試官都有自己的職責,一個求職者面試經歷的過程就好比一次客戶端的請求過程。
在設計模式系列的文章中之前已經為大家分享了創建型設計模式,感興趣的小伙伴們可以再去翻看之前的分享。接下來開始分享設計模式三大類型中的行為型模式了,今天要分享的是責任鏈模式
大綱
定義
什么是責任鏈?它的原理是什么?
- 將請求的發送和接收解耦,讓多個接收對象都有機會處理這個請求。將這些接收對象串成一條鏈,并沿著這條鏈傳遞這個請求,直到鏈上的某個接收對象能夠處理它為止。
- 以上定義來自《設計模式之美》
再看看一張官方圖解吧
- Client(客戶端):實例化一個處理器的鏈,在第一個鏈對象中調用handleRequest 方法。
- Handle(處理器):抽象類,提供給實際處理器繼承然后實現handleRequst方法,處理請求
- ConcreteHandler(具體處理器):繼承了handler的類,同時實現handleRequst方法,負責處理業務邏輯類,不同業務模塊有不同的ConcreteHandler。
這么看結構其實還是比較簡單的,但是我們還是拿面試的流程來模擬一下責任鏈吧!
代碼實現
假設現在去一家公司面試,第一次去一面,第二次去二面,第三次去直接過了。那這個模擬面試代碼怎么寫呢?
- public abstract class Handler {
- protected Handler handler;
- public void setHandler(Handler handler) {
- this.handler = handler;
- }
- public abstract void handleRequest(Integer times);
- }
首先我們還是定義一個抽象Handler處理器,同時添加一個抽象處理方法 handleRequest,后面我只需要編寫具體的處理器來繼承Handler類
- public class FirstInterview extends Handler {
- @Override
- public void handleRequest(Integer times) {
- // 條件判斷是否是屬于當前Handler的處理范圍之內,不是則向下傳遞Handler處理器
- if(times ==1){
- // 假設這里是處理的業務邏輯代碼
- System.out.println("第一次面試"+times);
- }
- handler.handleRequest(times);
- }
- }
其次構建第一次面試Handler,內部實現handleRequest方法,判斷一下是否是當前處理應該處理的業務邏輯,不是則向下傳遞。同樣的第二次的SecondInterview和FirstInterview代碼基本是一致的,我就不給大家貼出來了,直接看最后一個
- public class ThreeInterview extends Handler {
- @Override
- public void handleRequest(Integer times) {
- if (times == 3) {
- System.out.println("第三次面試"+ times + ",恭喜面試通過,HR會跟你聯 系!!!");
- }
- }
- public static void main(String[] args) {
- Handler first = new FirstInterview();
- Handler second = new SecondInterview();
- Handler three = new ThreeInterview();
- first.setHandler(second);
- second.setHandler(three);
- // 第一次面試
- first.handleRequest(1);
- System.out.println();
- // 第二次面試
- first.handleRequest(2);
- System.out.println();
- // 第三次面試
- first.handleRequest(3);
- System.out.println();
- }
- }
這個結果可以很明顯的看出,根據我們傳參,不同的Handler根據自己的職責處理著自己的業務,這就是責任鏈。
框架的應用
責任鏈在很多框架源碼中也有體現。比如開始學SpringMVC中的 ServletFilter
以及Spring中的 SpringInterceptor 這里面其實都是運用了責任鏈模式的思想,達到框架的可擴展性的同時也遵循著開閉原則。
作為常見的RPC框架的DUBBO其實里面也同樣有這個責任鏈的思想。
給大家一個思考問題?
- dubbo服務一旦暴露出去了,那么基本任何服務都能調用,但是在一些特殊的業務中需要我們暴露服務,但是又不希望被不了解業務的人隨便調用。
- 比如:商品的庫存修改的dubbo服務,我們只允許下單,購物車,添加修改商品等一些指定場景可以調用。
- 那么有什么辦法,在Provider這端做好攔截,針對特定的服務才允許調用,否則攔截下來不允許執行?
第一種方法,添加服務名稱APP_NAME作為傳參校驗,這是很常見也最容易想到的辦法。
第二種方法,實現一個DUBBO攔截器,對RPC調用進行選擇性過濾。
針對上面的兩種方法,給大家詳細講講第二種方法具體怎么實現,每個公司都會基于現有的DUBBO源碼做自己的特定化改動,那么第二種方式也是同樣需要我們改動線有dubbo源碼。
先修改ConsumerContextFilter消費者攔截器
這里我們以dubbo的2.7.19版本為例。在ConsumerContextFilter中添加APP_NAME至Attachments中,那么作為本次的RPC調用都能從Attachments中獲取到我們塞入的值。
至于這個APP_NAME的獲取 可以通過 System.getProperty("project.name", "") 來獲取服務名
- 這里我就不對DUBBO做過多的展開,大家如果有強烈建議講解。那么在結束設計模式再跟大家詳細剖析一下dubbo,以及zookeeper里面的ZAB,一致性選舉算法等等。
CONSUMER既然已經填充了服務名稱,那么在Provider同樣的也就只需要寫一個ProviderFilter 就可以了
這里就基本實現怎么處理每一次RPC調用的攔截了,然后想要那個服務攔截,在provider里面的filter里面指定一下這個DubboProviderFilter就可以了,也可以全局都實現。
注意 :這個Filter 要是用DUBBO包里面的,不要搞錯了。
現實業務改造舉例
框架中既然都有這種思想,那么怎么運用到業務代碼中呢?
還是給大家舉一個例子:
商品詳情展示我們可以是分模塊展示的,比如頭圖,商品信息,sku信息,配送地址,分期付費等等。
那么怎么進行組裝到商品詳情的展示呢?
- public abstract class AbstractDataHandler<T> {
- // 處理模塊化數據
- protected abstract T doRequest(String query) throws Exception;
- }
首先我們還是定一個抽象數據Handler,然后分別建立ItemInfoHandler 和SkuInfoHandler 來繼承抽象處理器
- @Component
- public class ItemInfoHandler extends AbstractDataHandler<ItemInfoHandler.ItemInfo> {
- @Override
- protected ItemInfoHandler.ItemInfo doRequest(String query) {
- ItemInfoHandler.ItemInfo info = new ItemInfo();
- info.setItemId(123456L);
- info.setItemName("測試商品");
- return info;
- }
- @Data
- public static class ItemInfo {
- private Long itemId;
- private String itemName;
- }
- }
同樣SkuInfoHandler類也是一樣的
- @Component
- public class SkuInfoHandler extends AbstractDataHandler<SkuInfoHandler.SkuInfo> {
- @Override
- protected SkuInfoHandler.SkuInfo doRequest(String query) {
- SkuInfoHandler.SkuInfo info = new SkuInfoHandler.SkuInfo();
- info.setSkuId(78910L);
- info.setSkuName("測試SKU");
- return info;
- }
- @Data
- public static class SkuInfo {
- private Long skuId;
- private String skuName;
- }
- }
最后就是我們的測試代碼了
- @Component
- public class DataAggregation {
- @Autowired
- private SkuInfoHandler skuInfoHandler;
- @Autowired
- private ItemInfoHandler itemInfoHandler;
- public Map convertItemDetail() throws Exception {
- Map result = new HashMap();
- result.put("skuInfoHandler", skuInfoHandler.doRequest("模擬數據請求"));
- result.put("itemInfoHandler",itemInfoHandler.doRequest("模擬數據請求"));
- return result;
- }
- public static void main(String[] args) throws Exception {
- ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
- DataAggregation dataAggregation = (DataAggregation) applicationContext.getBean("dataAggregation");
- Map map = dataAggregation.convertItemDetail();
- System.out.println(JSON.toJSONString(map));
- // 打印的結果數據
- // {"skuInfoHandler":{"skuId":78910,"skuName":"測試SKU"},"itemInfoHandler":{"itemId":123456,"itemName":"測試商品"}}
- }
- }
這個例子其實是經過一點小小的改動的,我們沒有通過向下傳遞處理器的方式,而是通過實際業務邏輯在 convertItemDetail 的方法中去構建每個模塊的數據,最后返回出一個Map結構數據。
這里其實還有另外的一種寫法,把每一個需要處理的Handler 可以加載到一個List容器中,然后循環調用每個Handler中的doRequest方法,當然這是針對一些其他的業務場景這么寫。
看完大家也能發現其實每個Handler是可以共用的,每一塊業務的代碼邏輯非常的清晰,這樣的代碼寫出來就感覺很舒服了。
總結
設計模式不是一成不變的,只有適合自己當前業務的模式才是最好的模式。理解前輩的思想,組合我們自己需要的模式。
本次分享就到這里了,后面接著為大家分享行為型設計模式。