详细说说适配器模式

617 阅读8分钟

在软件开发中,有时候我们需要将一个已有的类或接口适配成另一个类或接口,以满足客户端的需求。这时候,适配器模式就可以派上用场了。适配器模式是一种常用的设计模式,它可以将一个类的接口转换成另一个客户端期望的接口,以便让两个不兼容的接口能够协同工作。适配器模式广泛应用于第三方API适配、代码重构、跨平台开发等场景。本文将介绍适配器模式的定义、结构、实现方式和应用场景,希望可以为读者带来启发和帮助。

什么是适配器模式

官方定义

适配器模式(Adapter Pattern)是一种将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能够在一起工作的设计模式。 ——《设计模式:可复用面向对象软件的基础》

简单来说

适配器模式就是将一个类的接口转换成另一个接口,以满足客户端的需求。适配器模式通常使用一个适配器来实现这种转换,适配器实现了目标接口,并包装了一个适配者对象,客户端通过调用适配器的接口来间接调用适配者对象的接口。

生活中的例子

适配器模式的一个常见例子是使用墙壁插座适配器来充电。在不同的国家和地区,电源插头和电压标准可能不同,因此在旅行或移居时,人们可能需要使用适配器来使自己的电子设备可以插入当地的插座并充电。

适配器模式的结构

适配器模式的结构通常包括三个主要部分:适配器(Adapter)、适配者(Adaptee)和目标(Target)。

  1. Target(目标):目标是客户端所期望的接口,它定义了客户端使用的方法和行为。

  2. Adaptee(适配者):适配者是客户端原本无法直接使用的接口,它需要被适配器适配为目标接口。

  3. Adapter(适配器):适配器是将适配者转换为目标接口的中间件,它实现了目标接口,并包装了一个适配者对象,在客户端调用目标接口时,适配器会间接调用适配者对象的方法,从而实现适配。

实现适配器模式

适配器有两种实现方式:

类适配器对象适配器两种实现方式

确定这三个组成部分一般以下这几个步骤:

  1. 确定目标接口:首先需要确定客户端所期望的目标接口,该接口应该定义客户端需要使用的方法和行为。(Target)

  2. 确定适配者:需要确定需要适配的适配者对象,即客户端无法直接使用的接口。(Adaptee)

  3. 实现适配器:适配器的实现是适配器模式的关键,它需要实现目标接口,并包装一个适配者对象,将适配者的接口转换为目标接口的接口。适配器通常采用继承或组合的方式实现,即继承适配者类或将适配者对象作为适配器的成员变量。(Adapter)

类适配器

比如要把110V的电压适配成220V的电压

Target(目标) : 定义客户端使用的方法

// 目标接口
public interface Target {
    int getVoltage();
}

Adaptee(适配者):客户端原本无法直接使用的接口即110V的电压

// 适配者类
public class Adaptee {
    public int getVoltage110() {
        return 110;
    }
}

Adapter(适配器): 实现了目标接口,继承适配类

public class Adapter extends Adaptee implements Target{
    @Override
    public int getVoltage() {
        // 将110V电压转换为220V电压
        return super.getVoltage110()*2;
    }
}

最后客户端调用

public class Client {
    public static void main(String[] args) {
        Adapter adapter = new Adapter();
        System.out.println(adapter.getVoltage());  //结果:220
    }
}

这样就利用适配器将110V转为220V了

类适配器是通过继承来实现适配器和适配者之间的关系,适配器类同时继承了适配者类和目标接口。因此,类适配器只能适配一个适配者类,而且需要注意适配者类的方法不应该和目标接口的方法冲突。此外,类适配器可以重写适配者类的方法来实现适配。

类适配器模式的经典类图

image.png

对象适配器

Target(目标) : 定义客户端使用的方法

// 目标接口
public interface Target {
    int getVoltage();
}

Adaptee(适配者):客户端原本无法直接使用的接口即110V的电压

// 适配者类
public class Adaptee {
    public int getVoltage110() {
        return 110;
    }
}

对象适配器的Adapter与类适配器不同

Adapter(适配器):实现Target,把适配者组合进Adapter

// 适配器类 (对象适配器)
public class Adapter implements Target{

    private Adaptee adaptee;

    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public int getVoltage() {
        // 将110V电压转换为220V电压
        return adaptee.getVoltage110()*2;
    }
}

客户端:

public class Client {
    public static void main(String[] args) {
        // 创建适配者对象
        Adaptee adaptee = new Adaptee();
        // 创建适配器对象
        Adapter adapter = new Adapter(adaptee);
        // 测试适配器对象
        System.out.println(adapter.getVoltage()); //结果:220
    }
}

对象适配器是通过组合来实现适配器和适配者之间的关系,适配器类包含一个适配者类的实例,并实现了目标接口。由于适配器类和适配者类之间是委托关系,因此一个适配器类可以适配多个适配者类。此外,对象适配器无法重写适配者类的方法,但是可以通过调用适配者类的方法来实现适配。

对象适配器经典类图

image.png

总的来说,类适配器和对象适配器都有各自的优缺点,需要根据具体的场景来选择使用哪一种。如果适配者类比较稳定,且只需要适配一个适配者类,那么可以选择类适配器。如果适配者类比较多,或者需要动态适配适配者类,那么可以选择对象适配器。

实际应用场景

  1. 第三方API适配:在使用第三方API时,有可能第三方API的接口与我们需要的接口不一致,这时可以使用适配器模式将第三方API的接口适配为我们需要的接口。

  2. 代码重构:在对已有代码进行重构时,有可能发现接口已经改变,但是为了兼容旧的代码,需要保持旧接口不变。这时可以使用适配器模式,将新接口适配为旧接口。

  3. 跨平台开发:在进行跨平台开发时,不同的平台之间可能存在接口不兼容的情况,这时可以使用适配器模式,将不同平台的接口适配为相同的接口,以便在不同平台上复用代码。

  4. 数据库连接池适配:在使用不同的数据库连接池时,由于接口不同,需要对不同的数据库连接池进行适配,以便在不同的数据库连接池之间切换时不需要修改大量的代码。

  5. 设备驱动程序适配:在编写设备驱动程序时,有可能需要对不同的设备进行适配,以便在不同的设备之间切换时不需要修改大量的代码。

举一个springmvc的例子

RequestMappingHandlerAdapter类是适配器类,它实现了 HandlerAdapter 接口,将 HttpRequest 转换为 ModelAndView:

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements InitializingBean {
    
    // 实现 HandlerAdapter 接口的方法
    @Override
    public boolean supports(Object handler) {
        return handler instanceof HandlerMethod;
    }

    @Override
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 将请求转换为 HandlerMethod
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        // 执行 HandlerMethod 并返回 ModelAndView
        return handleInternal(request, response, handlerMethod);
    }
    
    // ...
}

HandlerAdapter 接口是目标接口,它是 Spring MVC 框架中用于适配各种类型的处理器方法的接口:

public interface HandlerAdapter {

    boolean supports(Object handler);

    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

    // ...
}

HandlerMethod 是适配者类,它是 Spring MVC 框架中的一个处理器方法的封装类:

public class HandlerMethod implements HandlerMethod {

    // 实现 HandlerMethod 接口的方法
    @Override
    public Object invoke(Object... args) throws Exception {
        // 调用实际的处理器方法
        return method.invoke(bean, args);
    }

    // ...
}

总结

适配器模式的优点包括:

  1. 提高了系统的灵活性和扩展性,可以在不修改原有代码的情况下引入新的适配器类,符合开闭原则。

  2. 可以让客户端使用统一的接口来调用不同类型的适配者,减少了客户端与各种适配者的耦合。

  3. 可以让适配者的接口与目标接口分离,符合单一职责原则和接口隔离原则。

适配器模式的缺点包括:

  1. 适配器过多会增加系统的复杂度和维护成本。

  2. 类适配器模式在适配者类的方法发生变化时可能会影响到目标接口,因为适配器类和适配者类是通过继承关系关联的。

  3. 对象适配器模式需要额外的适配器对象,增加了内存开销。