适配器模式(Adapter Pattern)

一、定义

适配器模式(Adapter Pattern):结构型模式之一,将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的哪些类可以一起工作

二、UML类图

适配器模式(Adapter Pattern)_第1张图片

三、角色职责

  • 目标角色(Target):该角色定义把其他类转换为何种接口,也就是我们的期望接口。
  • 源角色(Adaptee):你想把谁转换成目标角色,这个“谁”就是源角色,它是已经存在的、运行良好的类或对象。
  • 适配器角色(Adapter):适配器模式的核心角色,其他两个角色都是已经存在的角色,而适配器角色是需要新建立的,它的职责非常简单:通过继承或是类关联的方式把源角色转换为目标角色。

四、代码实现

前言:举个栗子,我今天买了机票,飞到香港迪士尼去游玩,晚上回到了酒店,想给我的笔记本电脑充电,但这时我发现,香港的插座是英式三角插座,我的充电器插不进去。这时我们就可以使用适配器模式,进行适配。

  • 类适配器:适配器通过类来实现,以类来继承和实现接口的方式,来获取被适配类的信息并转换输出重写到适配接口。

    中式插座(源角色 Adaptee)

    @AllArgsConstructor
    @Data
    public class ChineseStandard {
        public String getChineseStandard() {
            return "中式插座";
        }
    }
    

    英式插座(目标角色 Target)

    public interface BritishStandard {
        String getBritishStandard();
    }
    

    插座适配器(适配器角色 Adapter)

    public class StandardAdapter extends ChineseStandard implements BritishStandard {
        @Override
        public String getBritishStandard() {
            return this.getChineseStandard();
        }
    }
    

    笔记本电脑(客户端 Client)

    public class Notebook {
        public void charge(BritishStandard britishStandard) {
            if ("中式插座".equals(britishStandard.getBritishStandard())) {
                System.out.println("充电成功!");
            } else {
                System.out.println("充电失败!");
            }
        }
    }
    

    测试类

    public class AdapterTest {
        public static void main(String[] args) {
            // 充电成功!
            new Notebook().charge(new StandardAdapter());
        }
    }
    
  • 对象适配器:通过实例对象(构造器传递)来实现适配器,而不是再用继承,其余基本同类适配器。

    我么们将插座适配器就行修改即可

    @AllArgsConstructor
    public class StandardAdapter implements BritishStandard {
        private ChineseStandard chineseStandard;
    
        @Override
        public String getBritishStandard() {
            return chineseStandard.getChineseStandard();
        }
    }
    

    测试类

    public class AdapterTest {
        public static void main(String[] args) {
            // 充电成功!
            new Notebook().charge(new StandardAdapter(new ChineseStandard()));
        }
    }
    

    如果我们的源目标接口中还有一些其他我们不需要的方法,我们并不想去实现它,我们就可以将适配器作为一个抽象类,当我们实现适配器抽象类的时候只要重写我们需要的方法即可。这时候我们就用到了接口适配器。

  • 接口适配器:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求。

    英式插座(目标角色 Target)

    public interface BritishStandard {
        String getBritishStandard();
    
        String getTypeC();
    
        String getUSB();
    }
    

    插座适配器(适配器角色 Adapter)

    @AllArgsConstructor
    public abstract class StandardAdapter extends ChineseStandard implements BritishStandard {
    
        @Override
        public String getBritishStandard() {
            return null;
        }
    
        @Override
        public String getTypeC() {
            return null;
        }
    
        @Override
        public String getUSB() {
            return null;
        }
    }
    

    测试类

    public class AdapterTest {
        public static void main(String[] args) {
            StandardAdapter standardAdapter= new StandardAdapter() {
                @Override
                public String getBritishStandard() {
                    return new ChineseStandard().getChineseStandard();
                }
            };
            // 充电成功!
            new Notebook().charge(standardAdapter);
        }
    }
    

五、源码分析

我们先来看一下Spring MVC的工作原理
适配器模式(Adapter Pattern)_第2张图片

  1. 浏览器发送请求到 控制器(DispatcherServlet)
  2. 控制器 根据请求地址, 到 HandlerMapping(处理器映射) 寻找对应的 Handler(处理器)
  3. HanldlerMapping 返回 找到的Handler
  4. DispatcherServlet 根据找到的Handler 找对应的HandlerAdaptor
  5. 执行对应的Handler方法
  6. Handler 将执行结果 和 要响应的视图名 封装成 ModelAndView 对象
  7. 控制器根据返回的 ViewName 找对应的ViewResolver (视图解析ViewResolver 将 Model 渲染到 View 中
  8. 将渲染结果 返回给控制器
  9. 最终将结果响应给客户端浏览器

可以看出Spring MVC中的适配主要执行Controller的请求处理方法。在Spring MVC中,DispatcherServlet作为用户,HandlerAdapter作为期望接口(目标角色 Target),Controller则为源角色(Adaptee)。Spring MVC中的Controller种类众多,不同类型的Controller通过不同的方法来对请求进行处理。
我们首先看一下HandlerAdapter接口
适配器模式(Adapter Pattern)_第3张图片
Spring MVC提供的Controler如下。
适配器模式(Adapter Pattern)_第4张图片
Spring MVC提供的Adapter如下。适配器模式(Adapter Pattern)_第5张图片
该接口的每一个Controller都有一个适配器与之对应,这样的话,每自定义一个Controller需要定义一个实现HandlerAdapter的适配器。
我们进入DispatcherServlet类,查看是如何获得适配器的。
适配器模式(Adapter Pattern)_第6张图片
适配器模式(Adapter Pattern)_第7张图片
当Spring容器启动后,会将所有定义好的适配器对象存放在一个List集合中,当一个请求来临时,DispatcherServlet会通过 handler的类型找到对应适配器,并将该适配器对象返回给用户,然后就可以统一通过适配器的handle()方法来调用Controller中的用于处理请求的方法。通过适配器模式我们将所有的Controller统一交给 HandlerAdapter 处理,免去了写大量的 if-else 语句对 Controller进行判断,也更利于扩展新的Controller类型。

六、优缺点分析

类适配器
优点:可以根据需求重写Adaptee类的方法,使得Adapter的灵活性增强了。
缺点:有一定局限性。因为类适配器需要继承Target类,而Java是单继承机制,所以要求Adaptee类必须是接口。

对象适配器
优点:同一个Adapter可以把Adaptee类和他的子类都适配到目标接口。
缺点:需要重新定义Adaptee行为时,需要重新定义Adaptee的子类,并将适配器组合适配。

接口适配器
优点:可以灵活方便的选择性重写接口方法。
缺点:由于是匿名内部类的形式,所以不利于代码复用。

七、适用场景

  • 系统需要复用现有类,而该类的接口不符合系统的需求,可以使用适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
  • 多个组件功能类似,但接口不统一且可能会经常切换时,可使用适配器模式,使得客户端可以以统一的接口使用它们。

八、总结

适配器模式将现有接口转化为客户类所期望的接口,实现了对现有类的复用,它是一种使用频率非常高的设计模式,在软件开发中得以广泛应用,Spring等开源框架、驱动程序设计(如JDBC中的数据库驱动程序)中也都使用了适配器模式。

你可能感兴趣的:(设计模式,适配器模式)