溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java實(shí)踐之適配器模式的示例分析

發(fā)布時(shí)間:2021-06-21 10:55:53 來源:億速云 閱讀:206 作者:小新 欄目:開發(fā)技術(shù)

這篇文章給大家分享的是有關(guān)Java實(shí)踐之適配器模式的示例分析的內(nèi)容。小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過來看看吧。

    一、前言

    工作到3年左右很大一部分程序員都想提升自己的技術(shù)棧,開始嘗試去閱讀一些源碼,例如SpringMybaits、Dubbo等,但讀著讀著發(fā)現(xiàn)越來越難懂,一會(huì)從這過來一會(huì)跑到那去。甚至懷疑自己技術(shù)太差,慢慢也就不愿意再觸碰這部分知識(shí)。

    而這主要的原因是一個(gè)框架隨著時(shí)間的發(fā)展,它的復(fù)雜程度是越來越高的,從最開始只有一個(gè)非常核心的點(diǎn)到最后開枝散葉。這就像你自己開發(fā)的業(yè)務(wù)代碼或者某個(gè)組件一樣,最開始的那部分核心代碼也許只能占到20%,而其他大部分代碼都是為了保證核心流程能正常運(yùn)行的。所以這也是你讀源碼費(fèi)勁的一部分原因。

    框架中用到了設(shè)計(jì)模式嗎?

    框架中不僅用到設(shè)計(jì)模式還用了很多,而且有些時(shí)候根本不是一個(gè)模式的單獨(dú)使用,而是多種設(shè)計(jì)模式的綜合運(yùn)用。與大部分小伙伴平時(shí)開發(fā)的CRUD可就不一樣了,如果都是if語句從上到下,也就算得不上什么框架了。就像你到Spring的源碼中搜關(guān)鍵字Adapter,就會(huì)出現(xiàn)很多實(shí)現(xiàn)類,例如;UserCredentialsDataSourceAdapter。而這種設(shè)計(jì)模式就是我們本文要介紹的適配器模式。

    適配器在生活里隨處可見

    如果提到在日常生活中就很多適配器的存在你會(huì)想到什么?在沒有看后文之前可以先思考下。

    二、適配器模式介紹

    Java實(shí)踐之適配器模式的示例分析

    適配器模式的主要作用就是把原本不兼容的接口,通過適配修改做到統(tǒng)一。使得用戶方便使用,就像我們提到的萬能充、數(shù)據(jù)線、MAC筆記本的轉(zhuǎn)換頭、出國旅游買個(gè)插座等等,他們都是為了適配各種不同的,做的兼容。。

    Java實(shí)踐之適配器模式的示例分析

    除了我們生活中出現(xiàn)的各種適配的場(chǎng)景,那么在業(yè)務(wù)開發(fā)中呢?

    在業(yè)務(wù)開發(fā)中我們會(huì)經(jīng)常的需要做不同接口的兼容,尤其是中臺(tái)服務(wù),中臺(tái)需要把各個(gè)業(yè)務(wù)線的各種類型服務(wù)做統(tǒng)一包裝,再對(duì)外提供接口進(jìn)行使用。而這在我們平常的開發(fā)中也是非常常見的。

    三、案例場(chǎng)景模擬

    Java實(shí)踐之適配器模式的示例分析

    隨著公司的業(yè)務(wù)的不斷發(fā)展,當(dāng)基礎(chǔ)的系統(tǒng)逐步成型以后。業(yè)務(wù)運(yùn)營就需要開始做用戶的拉新和促活,從而保障DAU的增速以及最終ROI轉(zhuǎn)換。

    而這時(shí)候就會(huì)需要做一些營銷系統(tǒng),大部分常見的都是裂變、拉客,例如;你邀請(qǐng)一個(gè)用戶開戶、或者邀請(qǐng)一個(gè)用戶下單,那么平臺(tái)就會(huì)給你返利,多邀多得。同時(shí)隨著拉新的量越來越多開始設(shè)置每月下單都會(huì)給首單獎(jiǎng)勵(lì),等等,各種營銷場(chǎng)景。

    那么這個(gè)時(shí)候做這樣一個(gè)系統(tǒng)就會(huì)接收各種各樣的MQ消息或者接口,如果一個(gè)個(gè)的去開發(fā),就會(huì)耗費(fèi)很大的成本,同時(shí)對(duì)于后期的拓展也有一定的難度。此時(shí)就會(huì)希望有一個(gè)系統(tǒng)可以配置一下就把外部的MQ接入進(jìn)行,這些MQ就像上面提到的可能是一些注冊(cè)開戶消息、商品下單消息等等。

    而適配器的思想方式也恰恰可以運(yùn)用到這里,并且我想強(qiáng)調(diào)一下,適配器不只是可以適配接口往往還可以適配一些屬性信息。

    3.1、場(chǎng)景模擬工程

    itstack-demo-design-6-00

    └── src

        └── main

            └── java

                └── org.itstack.demo.design

                    ├── mq

                    │   ├── create_account.java

                    │   ├── OrderMq.java

                    │   └── POPOrderDelivered.java

                    └── service

                        ├── OrderServicejava

                        └── POPOrderService.java

    這里模擬了三個(gè)不同類型的MQ消息,而在消息體中都有一些必要的字段,比如;用戶ID、時(shí)間、業(yè)務(wù)ID,但是每個(gè)MQ的字段屬性并不一樣。就像用戶ID在不同的MQ里也有不同的字段:uId、userId等。同時(shí)還提供了兩個(gè)不同類型的接口,一個(gè)用于查詢內(nèi)部訂單訂單下單數(shù)量,一個(gè)用于查詢第三方是否首單。后面會(huì)把這些不同類型的MQ和接口做適配兼容。

    3.2、場(chǎng)景簡(jiǎn)述

    3.2.1、注冊(cè)開戶MQ
    public class create_account {
    
        private String number;      // 開戶編號(hào)
        private String address;     // 開戶地
        private Date accountDate;   // 開戶時(shí)間
        private String desc;        // 開戶描述
    
        // ... get/set     
    }
    3.2.2、內(nèi)部訂單MQ
    public class OrderMq {
    
        private String uid;           // 用戶ID
        private String sku;           // 商品
        private String orderId;       // 訂單ID
        private Date createOrderTime; // 下單時(shí)間     
    
        // ... get/set      
    }
    3.2.3、第三方訂單MQ
    public class POPOrderDelivered {
    
        private String uId;     // 用戶ID
        private String orderId; // 訂單號(hào)
        private Date orderTime; // 下單時(shí)間
        private Date sku;       // 商品
        private Date skuName;   // 商品名稱
        private BigDecimal decimal; // 金額
    
        // ... get/set      
    }
    3.2.4、查詢用戶內(nèi)部下單數(shù)量接口
    public class OrderService {
    
        private Logger logger = LoggerFactory.getLogger(POPOrderService.class);
    
        public long queryUserOrderCount(String userId){
            logger.info("自營商家,查詢用戶的訂單是否為首單:{}", userId);
            return 10L;
        }
    
    }
    3.2.5、查詢用戶第三方下單首單接口
    public class POPOrderService {
    
        private Logger logger = LoggerFactory.getLogger(POPOrderService.class);
    
        public boolean isFirstOrder(String uId) {
            logger.info("POP商家,查詢用戶的訂單是否為首單:{}", uId);
            return true;
        }
    
    }

    以上這幾項(xiàng)就是不同的MQ以及不同的接口的一個(gè)體現(xiàn),后面我們將使用這樣的MQ消息和接口,給它們做相應(yīng)的適配。

    四、代碼實(shí)現(xiàn)

    其實(shí)大部分時(shí)候接MQ消息都是創(chuàng)建一個(gè)類用于消費(fèi),通過轉(zhuǎn)換他的MQ消息屬性給自己的方法。

    我們接下來也是先體現(xiàn)一下這種方式的實(shí)現(xiàn)模擬,但是這樣的實(shí)現(xiàn)有一個(gè)很大的問題就是,當(dāng)MQ消息越來越多后,甚至幾十幾百以后,你作為中臺(tái)要怎么優(yōu)化呢?

    4.1、工程結(jié)構(gòu)

    itstack-demo-design-6-01

    └── src

        └── main

            └── java

                └── org.itstack.demo.design

                    └── create_accountMqService.java

                    └── OrderMqService.java

                    └── POPOrderDeliveredService.java

    目前需要接收三個(gè)MQ消息,所有就有了三個(gè)對(duì)應(yīng)的類,和我們平時(shí)的代碼幾乎一樣。如果你的MQ量不多,這樣的寫法也沒什么問題,但是隨著數(shù)量的增加,就需要考慮用一些設(shè)計(jì)模式來解決。

    4.2、Mq接收消息實(shí)現(xiàn)

    public class create_accountMqService {
    
        public void onMessage(String message) {
    
            create_account mq = JSON.parseObject(message, create_account.class);
    
            mq.getNumber();
            mq.getAccountDate();
    
            // ... 處理自己的業(yè)務(wù)
        }
    
    }

    三組MQ的消息都是一樣模擬使用,就不一一展示了。可以獲取源碼后學(xué)習(xí)。

    五、適配器模式重構(gòu)代碼

    接下來使用適配器模式來進(jìn)行代碼優(yōu)化,也算是一次很小的重構(gòu)。

    適配器模式要解決的主要問題就是多種差異化類型的接口做統(tǒng)一輸出,這在我們學(xué)習(xí)工廠方法模式中也有所提到不同種類的獎(jiǎng)品處理,其實(shí)那也是適配器的應(yīng)用。

    在本文中我們還會(huì)再另外體現(xiàn)出一個(gè)多種MQ接收,使用MQ的場(chǎng)景。來把不同類型的消息做統(tǒng)一的處理,便于減少后續(xù)對(duì)MQ接收。

    在這里如果你之前沒要開發(fā)過接收MQ消息,可能聽上去會(huì)有些不理解這樣的場(chǎng)景。對(duì)此,我個(gè)人建議先了解下MQ。另外就算不了解也沒關(guān)系,不會(huì)影響對(duì)思路的體會(huì)。

    再者,本文所展示的MQ兼容的核心部分,也就是處理適配不同的類型字段。而如果我們接收MQ后,在配置不同的消費(fèi)類時(shí),如果不希望一個(gè)個(gè)開發(fā)類,那么可以使用代理類的方式進(jìn)行處理。

    5.1、工程結(jié)構(gòu)

    itstack-demo-design-6-02

    └── src

        └── main

            └── java

                └── org.itstack.demo.design

                    ├── impl

                    │   ├── InsideOrderService.java

                    │   └── POPOrderAdapterServiceImpl.java

                    ├── MQAdapter,java

                    ├── OrderAdapterService,java

                    └── RebateInfo,java

    適配器模型結(jié)構(gòu)

    Java實(shí)踐之適配器模式的示例分析

    • 這里包括了兩個(gè)類型的適配;接口適配、MQ適配。之所以不只是模擬接口適配,因?yàn)楹芏鄷r(shí)候大家都很常見了,所以把適配的思想換一下到MQ消息體上,增加大家多設(shè)計(jì)模式的認(rèn)知。

    • 先是做MQ適配,接收各種各樣的MQ消息。當(dāng)業(yè)務(wù)發(fā)展的很快,需要對(duì)下單用戶首單才給獎(jiǎng)勵(lì),在這樣的場(chǎng)景下再增加對(duì)接口的適配操作。

    5.2、代碼實(shí)現(xiàn)(MQ消息適配)

    5.2.1、統(tǒng)一的MQ消息體
    public class RebateInfo {
    
        private String userId;  // 用戶ID
        private String bizId;   // 業(yè)務(wù)ID
        private Date bizTime;   // 業(yè)務(wù)時(shí)間
        private String desc;    // 業(yè)務(wù)描述
        
        // ... get/set
    }
    • MQ消息中會(huì)有多種多樣的類型屬性,雖然他們都有同樣的值提供給使用方,但是如果都這樣接入那么當(dāng)MQ消息特別多時(shí)候就會(huì)很麻煩。

    • 所以在這個(gè)案例中我們定義了通用的MQ消息體,后續(xù)把所有接入進(jìn)來的消息進(jìn)行統(tǒng)一的處理。

    5.2.2、MQ消息體適配類
    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;
        }
    
    }
    • 這個(gè)類里的方法非常重要,主要用于把不同類型MQ種的各種屬性,映射成我們需要的屬性并返回。就像一個(gè)屬性中有用戶ID;uId,映射到我們需要的;userId,做統(tǒng)一處理。

    • 而在這個(gè)處理過程中需要把映射管理傳遞給Map<String, String> link,也就是準(zhǔn)確的描述了,當(dāng)前MQ中某個(gè)屬性名稱,映射為我們的某個(gè)屬性名稱。

    • 最終因?yàn)槲覀兘邮盏降?code>mq消息基本都是json格式,可以轉(zhuǎn)換為MAP結(jié)構(gòu)。最后使用反射調(diào)用的方式給我們的類型賦值。

    5.2.3、測(cè)試適配類

    編寫單元測(cè)試類

    @Test
    public void test_MQAdapter() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        create_account create_account = new create_account();
        create_account.setNumber("100001");
        create_account.setAddress("河北省.廊坊市.廣陽區(qū).大學(xué)里職業(yè)技術(shù)學(xué)院");
        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));
    }
    • 在這里我們分別模擬傳入了兩個(gè)不同的MQ消息,并設(shè)置字段的映射關(guān)系。

    • 等真的業(yè)務(wù)場(chǎng)景開發(fā)中,就可以配這種映射配置關(guān)系交給配置文件或者數(shù)據(jù)庫后臺(tái)配置,減少編碼。

    測(cè)試結(jié)果

    mq.create_account(適配前){"accountDate":1591024816000,"address":"河北省.廊坊市.廣陽區(qū).大學(xué)里職業(yè)技術(shù)學(xué)院","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

    • 從上面可以看到,同樣的字段值在做了適配前后分別有統(tǒng)一的字段屬性,進(jìn)行處理。這樣業(yè)務(wù)開發(fā)中也就非常簡(jiǎn)單了。

    • 另外有一個(gè)非常重要的地方,在實(shí)際業(yè)務(wù)開發(fā)中,除了反射的使用外,還可以加入代理類把映射的配置交給它。這樣就可以不需要每一個(gè)mq都手動(dòng)創(chuàng)建類了。

    5.3、代碼實(shí)現(xiàn)(接口使用適配)

    就像我們前面提到隨著業(yè)務(wù)的發(fā)展,營銷活動(dòng)本身要修改,不能只是接了MQ就發(fā)獎(jiǎng)勵(lì)。因?yàn)榇藭r(shí)已經(jīng)拉新的越來越多了,需要做一些限制。

    因?yàn)樵黾恿酥挥惺讍斡脩舨沤o獎(jiǎng)勵(lì),也就是你一年或者新人或者一個(gè)月的第一單才給你獎(jiǎng)勵(lì),而不是你之前每一次下單都給獎(jiǎng)勵(lì)。

    那么就需要對(duì)此種方式進(jìn)行限制,而此時(shí)MQ中并沒有判斷首單的屬性。只能通過接口進(jìn)行查詢,而拿到的接口如下;

    接口描述
    org.itstack.demo.design.service.OrderService.queryUserOrderCount(String userId)出參long,查詢訂單數(shù)量
    org.itstack.demo.design.service.OrderService.POPOrderService.isFirstOrder(String uId)出參boolean,判斷是否首單
    • 兩個(gè)接口的判斷邏輯和使用方式都不同,不同的接口提供方,也有不同的出參。一個(gè)是直接判斷是否首單,另外一個(gè)需要根據(jù)訂單數(shù)量判斷。

    • 因此這里需要使用到適配器的模式來實(shí)現(xiàn),當(dāng)然如果你去編寫if語句也是可以實(shí)現(xiàn)的,但是我們經(jīng)常會(huì)提到這樣的代碼很難維護(hù)。

    5.3.1、定義統(tǒng)一適配接口
    public interface OrderAdapterService {
    
        boolean isFirst(String uId);
    
    }

    后面的實(shí)現(xiàn)類都需要完成此接口,并把具體的邏輯包裝到指定的類中,滿足單一職責(zé)。

    5.3.2、分別實(shí)現(xiàn)兩個(gè)不同的接口

    內(nèi)部商品接口

    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);
        }
    
    }

    在這兩個(gè)接口中都實(shí)現(xiàn)了各自的判斷方式,尤其像是提供訂單數(shù)量的接口,需要自己判斷當(dāng)前接到mq時(shí)訂單數(shù)量是否<= 1,以此判斷是否為首單。

    5.3.3、測(cè)試適配類

    編寫單元測(cè)試類

    @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"));
    }

    測(cè)試結(jié)果

    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

    從測(cè)試結(jié)果上來看,此時(shí)已經(jīng)的接口已經(jīng)做了統(tǒng)一的包裝,外部使用時(shí)候就不需要關(guān)心內(nèi)部的具體邏輯了。而且在調(diào)用的時(shí)候只需要傳入統(tǒng)一的參數(shù)即可,這樣就滿足了適配的作用。

    六、總結(jié)

    • 從上文可以看到不使用適配器模式這些功能同樣可以實(shí)現(xiàn),但是使用了適配器模式就可以讓代碼:干凈整潔易于維護(hù)、減少大量重復(fù)的判斷和使用、讓代碼更加易于維護(hù)和拓展。

    • 尤其是我們對(duì)MQ這樣的多種消息體中不同屬性同類的值,進(jìn)行適配再加上代理類,就可以使用簡(jiǎn)單的配置方式接入對(duì)方提供的MQ消息,而不需要大量重復(fù)的開發(fā)。非常利于拓展。

    • 設(shè)計(jì)模式的學(xué)習(xí)過程可能會(huì)在一些章節(jié)中涉及到其他設(shè)計(jì)模式的體現(xiàn),只不過不會(huì)重點(diǎn)講解,避免喧賓奪主。但在實(shí)際的使用中,往往很多設(shè)計(jì)模式是綜合使用的,并不會(huì)單一出現(xiàn)。

    感謝各位的閱讀!關(guān)于“Java實(shí)踐之適配器模式的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí),如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI