23 种设计模式--适配器模式(结构型模式一)

79 阅读6分钟

适配器模式(Adapter Pattern)充当两个不兼容接口之间的桥梁,属于结构型设计模式。它通过一个中间件(适配器)将一个类的接口转换成客户期望的另一个接口,使原本不能一起工作的类能够协同工作。

概述

适配器模式是一种软件设计模式,旨在解决不同接口之间的兼容性问题。

目的:将一个类的接口转换为另一个接口,使得原本不兼容的类可以协同工作,具体可分为类适配器对象适配器接口适配器

主要解决的问题:在软件系统中,需要将现有的对象放入新环境,而新环境要求的接口与现有对象不匹配。

使用场景

  • 需要使用现有类,但其接口不符合系统需求。
  • 希望创建一个可复用的类,与多个不相关的类(包括未来可能引入的类)一起工作,这些类可能没有统一的接口。
  • 通过接口转换,将一个类集成到另一个类系中。

实现方式

  • 继承或依赖:推荐使用依赖关系,而不是继承,以保持灵活性。

关键代码

适配器通过继承或依赖现有对象,并实现所需的目标接口。

使用建议

  • 适配器模式应谨慎使用,特别是在详细设计阶段,它更多地用于解决现有系统的问题,用于系统上线后,引入新模块或第三方库时,用来把“不兼容接口”强行适配,属于补救性设计。
  • 在考虑修改一个正常运行的系统接口时,适配器模式是一个合适的选择。

结构

适配器模式包含以下几个主要角色:

  • 目标接口(Target) :定义客户需要的接口。
  • 适配者类(Adaptee) :定义一个已经存在的接口,这个接口需要适配。
  • 适配器类(Adapter) :实现目标接口,并通过组合或继承的方式调用适配者类中的方法,从而实现目标接口。

代码实现

一、类适配器(通过继承方式来实现)【不推荐】

场景描述:用户新买了一件三孔插座的电器,但是家里只有一个两孔插座,这个时候怎么办呢?可以再买一个插座适配器,来做转化,把适配器插到两孔插座上,用户直接把电器插到适配器即可使用

  1. 目标接口:NewThreeHoleSocket
  2. 适配者类:OldTwoHoleSocket
  3. 适配器类:HoleSocketAdapter

public interface NewThreeHoleSocket {
    void threeHole();
}
public class OldTwoHoleSocket {
    public void twoHole() {
        System.out.println("插入现有的两孔插座");
    }
}
public class HoleSocketAdapter extends OldTwoHoleSocket implements NewThreeHoleSocket {

    @Override
    public void threeHole() {
        System.out.println("插入新增的三孔插座");
    }
}
public class ApiTest {
    public static void main(String[] args) {
        // 适配器对象,既可以使用三孔插座,也可以使用两孔插座
        HoleSocketAdapter holeSocketAdapter = new HoleSocketAdapter();
        // 使用新的三孔插座
        holeSocketAdapter.threeHole();
        // 使用旧的两孔插座
        holeSocketAdapter.twoHole();
    }
}

二、对象适配器(通过依赖关系来实现)【推荐】

public interface NewThreeHoleSocket {
    void threeHole();
}
@Component
public class OldTwoHoleSocket {
    public void twoHole() {
        System.out.println("插入现有的两孔插座");
    }
}
@Component
@RequiredArgsConstructor
public class HoleSocketAdapter implements NewThreeHoleSocket {
    // 依赖旧的二孔插座
    private final OldTwoHoleSocket oldTwoHoleSocket;

    @Override
    public void threeHole() {
        System.out.println("插入新增的三孔插座");
    }

    public void twoHole() {
        oldTwoHoleSocket.twoHole();
    }
}
@Slf4j
@RestController
@RequestMapping("/portal/test")
public class TestController {
    @Resource
    private HoleSocketAdapter holeSocketAdapter;
    
    @GetMapping(value = "/get/adapter/test")
    public CommonResult<String> getAdapterTest() {
        // 使用二孔插座
        holeSocketAdapter.twoHole();
        // 使用三孔插座
        holeSocketAdapter.threeHole();
        return CommonResult.success();
    }
}

三、接口适配器

(一) 核心思想

当一个接口中定义了多个方法,而某个类只需要其中一部分方法时;可以通过引入一个抽象适配器类(Adapter Class)来实现该接口,并为所有方法提供空实现(或默认实现)。这样,具体子类只需重写自己关心的方法即可,避免实现所有不必要的方法。

(二) 适用场景

  • 接口方法过多,但实现类只关心其中部分方法;
  • 避免强制实现类编写大量无用的空方法;
  • 提高代码可维护性和可读性。

(三) 结构

接口适配器模式包含以下几个主要角色:

  • 目标接口(Target) :定义客户需要的接口;
  • 适配器抽象类(Adapter) :抽象类,实现 Target Interface,提供所有方法的默认/空实现;
  • 适配器具体的子实现类(Concrete Class) :继承 Adapter,只重写需要的方法。
public interface HoleSocket {
    // 二孔排插
    void twoHole();
    // 三孔排插
    void threeHole();
    
    void usbHole();
}
public abstract class BaseHoleSocket implements HoleSocket{
    @Override
    public void twoHole() {

    }

    @Override
    public void threeHole() {

    }

    @Override
    public void usbHole() {

    }
}
@Component
public class HoleSocketAdapter extends BaseHoleSocket {
    @Override
    public void threeHole() {
        System.out.println("插入新增的三孔插座");
    }
}
@Slf4j
@RestController
@RequestMapping("/portal/test")
public class TestController {
    @Resource
    private HoleSocketAdapter holeSocketAdapter;
    
    @GetMapping(value = "/get/adapter/test")
    public CommonResult<String> getAdapterTest() {
        // 使用三孔插座
        holeSocketAdapter.threeHole();
        return CommonResult.success();
    }
}

(三)接口适配器在框架中的应用场景

系统/框架适配器类解决的问题
Java AWT/SwingWindowAdapter, MouseAdapter避免实现所有事件监听方法
Spring (旧版)HandlerInterceptorAdapter只处理拦截器中的部分阶段
NettyChannelInboundHandlerAdapter只处理感兴趣的网络事件
自定义 GUI/事件系统各类 XxxListenerAdapter用户只实现关心的回调

三种适配器对比

对比维度类适配器对象适配器接口适配器
核心实现方式通过多重继承(适配器实现目标接口 + 继承被适配类)通过组合(适配器实现目标接口 + 在适配器中引入被适配类对象)通过抽象类实现接口,提供空/默认实现
是否继承被适配类✔ 是(直接继承)❌ 否(通过组合引用)❌ 不涉及被适配类,只对接口“瘦身”
耦合度🔥 高(编译期绑定具体被适配类)⚡ 低(运行时可替换被适配对象)🌱 极低(仅简化接口实现,不涉及外部类)
灵活性🌱 低(只能适配一个具体类)🔥 高(可适配任意子类或实现)⚡ 中(仅用于减少接口实现负担)
典型代码结构class Adapter extends Adaptee implements Targetclass Adapter implements Target {private Adaptee adaptee;}abstract class Adapter implements Target {public void method1() {};......}
能否适配多个被适配类❌ 不能(受限于单继承)✔ 可以(通过更换内部引用)❌ 不适用(不处理被适配类)
主要目的将一个类的接口转换为客户期望的另一个接口同左,但更灵活安全避免实现接口中所有方法,只重写关心的方法
经典应用场景C++ 中集成旧类库(Java 中基本不用)1、集成第三方 SDK(如支付、地图)2、统一多数据源(CSV/JSON/API)3、Spring 的 HttpMessageConverter4、JDBC 驱动桥接1、Java AWT/Swing 事件监听(WindowAdapter, MouseAdapter)2、自定义监听器只需处理部分事件
是否修改原有代码❌ 不需要(但需能继承)❌ 不需要❌ 不需要
是否符合开闭原则★ 较弱(绑定具体类)★★★ 强(对扩展开放)★★★ 强(新增子类即可)
在 Java 中的实用性❌ 几乎不可用(无多重继承)✔ 最常用、最推荐✔ 在 Java 8 之前广泛使用;Java 8+ 可用 default() 方法替代