设计模式 - 结构型模式_适配器模式

简介: 适配器模式的主要作⽤就是把原本不兼容的接⼝,通过适配修改做到统⼀。使得⽤户⽅便使⽤,就像我们提到转换头、出国旅游买个插座等等,都是为了适配各种不同的口 ,做的兼容。 在业务开发中我们会经常的需要做不同接⼝的兼容,尤其是中台服务,中台需要把各个业务线的各种类型服务做统⼀包装,再对外提供接⼝进⾏使⽤。⽽这在平常的开发中也是⾮常常⻅的。

@[toc]

在这里插入图片描述


结构型模式

结构型模式主要是解决如何将对象和类组装成较大的结构, 并同时保持结构的灵活和⾼效。

结构型模式包括:适配器、桥接、组合、装饰器、外观、享元、代理,这7类

在这里插入图片描述


概述

在这里插入图片描述
适配器模式的主要作⽤就是把原本不兼容的接⼝,通过适配修改做到统⼀。使得⽤户⽅便使⽤,就像我们提到转换头、出国旅游买个插座等等,都是为了适配各种不同的口 ,做的兼容。

在业务开发中我们会经常的需要做不同接⼝的兼容,尤其是中台服务,中台需要把各个业务线的各种类型服务做统⼀包装,再对外提供接⼝进⾏使⽤。⽽这在平常的开发中也是⾮常常⻅的。


Case

在这里插入图片描述

营销系统 接收各种各样的MQ消息或者接⼝,如果⼀个个的去开发,就会耗费很⼤的成本,同时对于后期的拓展也有⼀定的难度。

此时就会希望有⼀个系统可以配置⼀下就把外部的MQ接⼊进⾏,这些MQ就像上⾯提到的可能是⼀些注册开户消息、商品下单消息等等。


场景模拟⼯程

在这里插入图片描述

  • 模拟了三个不同类型的MQ消息,⽽在消息体中都有⼀些必要的字段,⽐如: ⽤户ID、时间、业务ID,但是每个MQ的字段属性并不⼀样。就像⽤户ID在不同的MQ⾥也有不同的字段:uId、userId等。
  • 同时还提供了两个不同类型的接⼝,⼀个⽤于查询内部订单订单下单数量,⼀个⽤于查询第三⽅是否⾸单。
  • 后⾯会把这些不同类型的MQ和接⼝做适配兼容。

【注册开户MQ】

@Data
@NoArgsConstructor
@AllArgsConstructor
public class CreateAccount {

    /**
     * 开户编号
     */
    private String number;

    /**
     * 开户地
     */
    private String address;


    /**
     * 开户时间
     */
    private Date accountDate;


    /**
     * 开户描述
     */
    private String desc;


    @Override
    public String toString() {
        return JSON.toJSONString(this);
    }
}

【内部订单MQ】

@Data
@NoArgsConstructor
@AllArgsConstructor
public class OrderMq {

    /**
     * 用户ID
     */
    private String uid;

    /**
     * 商品
     */
    private String sku;

    /**
     * 订单ID
     */
    private String orderId;

    /**
     * 下单时间
     */
    private Date createOrderTime;


    @Override
    public String toString() {
        return JSON.toJSONString(this);
    }
}

【第三⽅订单MQ】

@Data
@NoArgsConstructor
@AllArgsConstructor
public class POPOrderDelivered {

    /**
     * 用户ID
     */
    private String uId;

    /**
     * 订单号
     */
    private String orderId;

    /**
     * 下单时间
     */
    private Date orderTime;

    /**
     * 商品
     */
    private Date sku;

    /**
     * 商品名称
     */
    private Date skuName;


    /**
     * 金额
     */
    private BigDecimal decimal;


    @Override
    public String toString() {
        return JSON.toJSONString(this);
    }

}

【查询⽤户内部下单数量接⼝】

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消息和接⼝,给它们做相应的适配。


Bad Impl

其实⼤部分时候接MQ消息都是创建⼀个类⽤于消费,通过转换他的MQ消息属性给⾃⼰的⽅法。

我们接下来也是先体现⼀下这种⽅式的实现模拟,但是这样的实现有⼀个很⼤的问题就是,当MQ消息越来越多后,甚⾄⼏⼗⼏百以后,作为中台系统要怎么优化呢?

在这里插入图片描述

⽬前需要接收三个MQ消息,所有就有了三个对应的类,和我们平时的代码⼏乎⼀样。如果MQ量不多,这样的写法也没什么问题,但是随着数量的增加,就需要考虑⽤⼀些设计模式来解决。

public class CreateAccountMqService {

    public void onMessage(String message) {

        CreateAccount mq = JSON.parseObject(message, CreateAccount.class);

        mq.getNumber();
        mq.getAccountDate();

        // ... 处理自己的业务
    }

}

Better Impl (适配器模式重构代码)

接下来使⽤适配器模式来进⾏代码优化,也算是⼀次很⼩的重构。

适配器模式要解决的主要问题就是多种差异化类型的接⼝做统⼀输出。 把不同类型的消息做统⼀的处理,便于减少后续对MQ接收。如果我们接收MQ后,在配置不同的消费类时,如果不希望⼀个个开发类,那么可以使⽤代理类的⽅式进⾏处理。

【工程结构】

在这里插入图片描述

【适配器模型结构】

在这里插入图片描述

  • 这⾥包括了两个类型的适配: 接⼝适配、MQ适配。之所以不只是模拟接⼝适配,因为很常⻅了,所以把适配的思想换⼀下到MQ消息体上,增加对设计模式的认知。
  • 先是做MQ适配,接收各种各样的MQ消息。当业务发展的很快,需要对下单⽤户⾸单才给奖励,在这样的场景下再增加对接⼝的适配操作。

MQ消息适配

【统⼀的MQ消息体】

public class RebateInfo {

    private String userId;  // 用户ID
    private String bizId;   // 业务ID
    private Date bizTime;   // 业务时间
    private String desc;    // 业务描述

    // set get 
}
  • MQ消息中会有多种多样的类型属性,虽然他们都有同样的值提供给使⽤⽅,但是如果都这样接⼊那么当MQ消息特别多时候就会很麻烦。
  • 所以我们定义了通⽤的MQ消息体,后续把所有接⼊进来的消息进⾏统⼀的处理。

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

}
  • 这个类⾥的⽅法⾮常重要,主要⽤于把不同类型MQ种的各种属性,映射成我们需要的属性并返回。就像⼀个属性中有 ⽤户ID uId ,映射到我们需要的 userId ,做统⼀处理。
  • 在这个处理过程中需要把映射管理传递给 Map<String, String> link ,主要实现的功能是 当前MQ中某个属性名称映射为我们的某个属性名称。
  • mq 消息基本都是 json 格式,可以转换为MAP结构。最后使⽤反射调⽤的⽅式给我们的类型赋值。

【测试适配类】

 @Test
    public void test_MQAdapter() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, ParseException {

        SimpleDateFormat s = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date parse = s.parse("2020-06-01 23:20:16");


        CreateAccount create_account = new CreateAccount();
        create_account.setNumber("100001");
        create_account.setAddress("河北省.廊坊市.广阳区.大学里职业技术学院");
        create_account.setAccountDate(parse);
        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(parse);

        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消息,并设置字段的映射关系。
  • 业务场景开发中,就可以配这种映射配置关系交给配置⽂件或者数据库后台配置,减少编码。

在这里插入图片描述

  • 从上⾯可以看到,同样的字段值在做了适配前后分别有统⼀的字段属性,进⾏处理。这样业务开发中也就⾮常简单了。
  • 另外有⼀个⾮常᯿要的地⽅,在实际业务开发中,除了反射的使⽤外,还可以加⼊代理类把映射的配置交给它。这样就可以不需要每⼀个mq都⼿动创建类了。

接口适配

随着业务的发展,营销活动本身要修改,不能只是接了MQ就发奖励。因为此时已经拉新的越来越多了,需要做⼀些限制。

新需求: 只有⾸单⽤户才给奖励,那么就需要对此种⽅式进⾏限制,⽽此时MQ中并没有判断⾸单的属性。只能通过接⼝进⾏查询,⽽拿到的接⼝如下

  • OrderService.queryUserOrderCount(String userId) 出参long,查询订单数量
  • OrderService.POPOrderService.isFirstOrder(String uId) 出参boolean,判断是否⾸单
两个接⼝的判断逻辑和使⽤⽅式都不同,不同的接⼝提供⽅,也有不同的出参。⼀个是直接判断是否⾸单,另外⼀个需要根据订单数量判断。
这⾥需要使⽤到适配器的模式来实现,当然如果你去编写if语句也是可以实现的,但是我们经常会提到这样的代码很难维护。

【定义统⼀适配接⼝】

public interface OrderAdapterService {

    boolean isFirst(String uId);

}

后⾯的实现类都需要完成此接⼝,并把具体的逻辑包装到指定的类中,满⾜单⼀职责。


【分别实现两个不同的接⼝】

内部商品接⼝

public class InsideOrderServiceImpl 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);
    }

}

在这两个接⼝中都实现了各⾃的判断⽅式,尤其像是提供订单数量的接⼝,需要⾃⼰判断当前接到mq时订单数量是否 <= 1 ,以此判断是否为⾸单。


【单元测试】

   @Test
    public void test_itfAdapter() {
        OrderAdapterService popOrderAdapterService = new POPOrderAdapterServiceImpl();
        System.out.println("判断首单,接口适配(POP):" + popOrderAdapterService.isFirst("100001"));

        OrderAdapterService insideOrderService = new InsideOrderServiceImpl();
        System.out.println("判断首单,接口适配(自营):" + insideOrderService.isFirst("100001"));
    }

接⼝已经做了统⼀的包装,外部使⽤时候就不需要关⼼内部的具体逻辑了。⽽且在调⽤的时候只需要传⼊统⼀的参数即可,这样就满⾜了适配的作⽤。


小结

使⽤了适配器模式就可以让代码⼲净整洁易于维护、减少⼤量重复的判断和使⽤、让代码更加易于维护和拓展。尤其是对MQ这样的多种消息体中不同属性同类的值,进⾏适配再加上代理类,就可以使⽤简单的配置⽅式接⼊对⽅提供的MQ消息,⽽不需要⼤量重复的开发,⾮常利于拓展。

在这里插入图片描述

相关实践学习
消息队列RocketMQ版:基础消息收发功能体验
本实验场景介绍消息队列RocketMQ版的基础消息收发功能,涵盖实例创建、Topic、Group资源创建以及消息收发体验等基础功能模块。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
相关文章
|
2月前
|
设计模式 Java 程序员
Java设计模式-适配器模式(8)
Java设计模式-适配器模式(8)
|
1月前
|
设计模式 Java
Java设计模式之适配器模式
这篇文章详细讲解了Java设计模式中的适配器模式,包括其应用场景、实现方式及代码示例。
45 0
|
3月前
|
设计模式 存储 Java
【十】设计模式~~~结构型模式~~~享元模式(Java)
文章详细介绍了享元模式(Flyweight Pattern),这是一种对象结构型模式,通过共享技术实现大量细粒度对象的重用,区分内部状态和外部状态来减少内存中对象的数量,提高系统性能。通过围棋棋子的设计案例,展示了享元模式的动机、定义、结构、优点、缺点以及适用场景,并探讨了单纯享元模式和复合享元模式以及与其他模式的联用。
【十】设计模式~~~结构型模式~~~享元模式(Java)
|
3月前
|
设计模式 存储 Java
【九】设计模式~~~结构型模式~~~外观模式(Java)
文章详细介绍了外观模式(Facade Pattern),这是一种对象结构型模式,通过引入一个外观类来简化客户端与多个子系统之间的交互,降低系统的耦合度,并提供一个统一的高层接口来使用子系统。通过文件加密模块的实例,展示了外观模式的动机、定义、结构、优点、缺点以及适用场景,并讨论了如何通过引入抽象外观类来提高系统的可扩展性。
【九】设计模式~~~结构型模式~~~外观模式(Java)
|
3月前
|
设计模式 Java
【八】设计模式~~~结构型模式~~~装饰模式(Java)
文章详细介绍了装饰模式(Decorator Pattern),这是一种对象结构型模式,用于在不使用继承的情况下动态地给对象添加额外的职责。装饰模式通过关联机制,使用装饰器类来包装原有对象,并在运行时通过组合的方式扩展对象的行为。文章通过图形界面构件库的设计案例,展示了装饰模式的动机、定义、结构、优点、缺点以及适用场景,并提供了Java代码实现和应用示例。装饰模式提高了系统的灵活性和可扩展性,适用于需要动态、透明地扩展对象功能的情况。
【八】设计模式~~~结构型模式~~~装饰模式(Java)
|
3月前
|
设计模式 XML 存储
【七】设计模式~~~结构型模式~~~桥接模式(Java)
文章详细介绍了桥接模式(Bridge Pattern),这是一种对象结构型模式,用于将抽象部分与实现部分分离,使它们可以独立地变化。通过实际的软件开发案例,如跨平台视频播放器的设计,文章阐述了桥接模式的动机、定义、结构、优点、缺点以及适用场景,并提供了完整的代码实现和测试结果。桥接模式适用于存在两个独立变化维度的系统,可以提高系统的可扩展性和灵活性。
【七】设计模式~~~结构型模式~~~桥接模式(Java)
|
2月前
|
设计模式 Java
设计模式--适配器模式 Adapter Pattern
这篇文章介绍了适配器模式,包括其基本介绍、工作原理以及类适配器模式、对象适配器模式和接口适配器模式三种实现方式。
|
3月前
|
设计模式 XML 存储
【六】设计模式~~~结构型模式~~~适配器模式(Java)
文章详细介绍了适配器模式(Adapter Pattern),这是一种结构型设计模式,用于将一个类的接口转换成客户期望的另一个接口,使原本不兼容的接口能够一起工作,提高了类的复用性和系统的灵活性。通过对象适配器和类适配器两种实现方式,展示了适配器模式的代码应用,并讨论了其优点、缺点以及适用场景。
|
3月前
|
设计模式 缓存 Java
【十一】设计模式~~~结构型模式~~~代理模式(Java)
文章详细介绍了代理模式(Proxy Pattern),这是一种对象结构型模式,用于给对象提供一个代理以控制对它的访问。文中阐述了代理模式的动机、定义、结构、优点、缺点和适用环境,并探讨了远程代理、虚拟代理、保护代理等不同代理形式。通过一个商务信息查询系统的实例,展示了如何使用代理模式来增加身份验证和日志记录功能,同时保持客户端代码的无差别对待。此外,还讨论了代理模式在分布式技术和Spring AOP中的应用,以及动态代理的概念。
【十一】设计模式~~~结构型模式~~~代理模式(Java)
|
4月前
|
设计模式 Go 数据处理
iLogtail设计模式问题之在iLogtail中,为何需要使用适配器模式
iLogtail设计模式问题之在iLogtail中,为何需要使用适配器模式

热门文章

最新文章

  • 1
    C++一分钟之-设计模式:工厂模式与抽象工厂
    43
  • 2
    《手把手教你》系列基础篇(九十四)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-下篇(详解教程)
    50
  • 3
    C++一分钟之-C++中的设计模式:单例模式
    58
  • 4
    《手把手教你》系列基础篇(九十三)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-上篇(详解教程)
    38
  • 5
    《手把手教你》系列基础篇(九十二)-java+ selenium自动化测试-框架设计基础-POM设计模式简介(详解教程)
    64
  • 6
    Java面试题:结合设计模式与并发工具包实现高效缓存;多线程与内存管理优化实践;并发框架与设计模式在复杂系统中的应用
    59
  • 7
    Java面试题:设计模式在并发编程中的创新应用,Java内存管理与多线程工具类的综合应用,Java并发工具包与并发框架的创新应用
    42
  • 8
    Java面试题:如何使用设计模式优化多线程环境下的资源管理?Java内存模型与并发工具类的协同工作,描述ForkJoinPool的工作机制,并解释其在并行计算中的优势。如何根据任务特性调整线程池参数
    50
  • 9
    Java面试题:请列举三种常用的设计模式,并分别给出在Java中的应用场景?请分析Java内存管理中的主要问题,并提出相应的优化策略?请简述Java多线程编程中的常见问题,并给出解决方案
    112
  • 10
    Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
    78