一、结构型模式

在面向对象的世界里,如何优雅地组织类与对象、构建更大的结构,是每一位开发者都会反复思考的问题。直接堆砌类和继承固然简单,但当业务复杂度上升、类间关系变得盘根错节时,这种方式就会让代码变得臃肿、难以维护。

结构型设计模式正是为了解决这一痛点而诞生的一套思想体系。它们关注如何将类或对象按某种布局组合成更大的结构,通过组合、代理、适配和装饰等手段,让代码更具灵活性、可复用性和可维护性。

在 Java 开发中,结构型模式主要包含以下 7 种经典实现:

  1. 代理模式 (Proxy):为其他对象提供一种代理以控制对这个对象的访问,实现延迟加载、权限控制等。Java 设计模式・代理模式篇:从思想到代码实现-CSDN博客
  2. 适配器模式 (Adapter):将一个类的接口转换成客户期望的另一个接口,让原本不兼容的类可以协同工作。
  3. 装饰器模式 (Decorator):动态地给一个对象添加额外的职责,比生成子类更灵活地扩展功能。Java 设计模式・装饰器模式篇:从思想到代码实现-CSDN博客
  4. 桥接模式 (Bridge):将抽象部分与它的实现部分分离,使它们都可以独立地变化,避免类爆炸。Java 设计模式・桥接模式篇:从思想到代码实现-CSDN博客
  5. 外观模式 (Facade):为子系统中的一组接口提供一个统一的高层接口,使子系统更容易使用。Java 设计模式・外观模式篇:从思想到代码实现-CSDN博客
  6. 组合模式 (Composite):将对象组合成树形结构以表示 “部分 - 整体” 的层次关系,使客户端对单个对象和组合对象的使用具有一致性。Java 设计模式・组合模式篇:从思想到代码实现-CSDN博客
  7. 享元模式 (Flyweight):运用共享技术有效地支持大量细粒度的对象,减少内存占用。​Java 设计模式・享元模式篇:从思想到代码实现-CSDN博客

二、适配器模式

2.1 介绍

将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

适配器模式分为类适配器模式和对象适配器模式,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些

2.2 角色

  • 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。

  • 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。

  • 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。

三、类适配器

本文类名使用中文,方便读者理解

3.1 目标接口

public interface 中国插头 {
    public void useChinaPlug();
}

3.2 适配者类

public interface 外国插头 {
    public void useForeignPlug();
}
public class 使用外国插头 implements 外国插头{
    @Override
    public void useForeignPlug() {
        System.out.println("使用外国插头...");
    }
}

3.3 适配器类

public class 外国转中国适配器 extends 使用外国插头 implements 中国插头{
    @Override
    public void useChinaPlug() {
        useForeignPlug();
        System.out.println("将外国插头转为中国插头中...");
    }
}

3.4 客户端

public class 客户端 {
    public static void main(String[] args) {
        中国电器 item = new 中国电器();
        System.out.println("尝试使用外国插头...");
        item.use(new 外国转中国适配器());
    }
}

类适配器中适配器类继承了适配者类,违背了合成复用原则(优先使用对象组合而非类继承来实现代码复用)

四、对象适配器

4.1 目标接口,适配者类不变

4.2 适配器类

public class 外国转中国适配器 implements 中国插头{

    private 外国插头 foreignPlug ;

    public 外国转中国适配器(外国插头 foreignPlug) {
        this.foreignPlug = foreignPlug;
    }

    @Override
    public void useChinaPlug() {
        foreignPlug.useForeignPlug();
        System.out.println("将外国插头转为中国插头中...");
    }
}

4.3 客户端

public class 客户端 {
    public static void main(String[] args) {
        中国电器 item = new 中国电器();
        System.out.println("尝试使用外国插头...");
        item.use(new 外国转中国适配器(new 使用外国插头()));
    }
}

五、缺省适配器

假设你有一个包含 10 个方法的接口,但你只需要用到其中 1 个方法 —— 如果直接实现这个接口,必须空写剩下 9 个方法(否则报错),代码会充满冗余的空方法。缺省适配器就是为这个接口做一个 “中间类”,先实现所有方法(默认空实现 / 返回默认值),你只需继承这个中间类,重写需要的方法即可。

5.1 角色

  • 抽象接口(ServiceInterface):包含多个方法的复杂接口;
  • 缺省适配器(AbstractAdapter):实现抽象接口,给所有方法提供默认空实现;
  • 具体实现类(ConcreteClass):继承缺省适配器,只重写需要的方法。

5.2 代码实现

抽象接口

public interface 复杂接口 {
    void method1();

    void method2();

    void method3();
}

缺省适配器

public class 缺省适配器 implements 复杂接口{
    @Override
    public void method1() {

    }

    @Override
    public void method2() {

    }

    @Override
    public void method3() {

    }
}

具体实现类

public class 具体业务 extends 缺省适配器{

    @Override
    public void method1() {
        System.out.println("处理中...");
    }
}

六、对比

维度 类适配器 对象适配器 缺省适配器(默认适配器)
实现方式 继承(继承 Adaptee + 实现 Target) 组合(实现 Target + 持有 Adaptee 实例) 抽象类实现 Target 接口,提供所有方法的空 / 默认实现,子类按需重写
兼容性 单继承语言(如 Java)只能适配一个 Adaptee 可适配多个 Adaptee(持有多个实例) 适配复杂 Target 接口,降低实现门槛
灵活性 低(耦合度高,依赖继承) 高(耦合度低,可动态替换 Adaptee 实例) 中(通过继承适配器,灵活重写所需方法)
对 Adaptee 的扩展 可重写 Adaptee 的方法(因为继承) 无法重写 Adaptee 的方法(只能调用) 不直接扩展 Adaptee,而是简化 Target 接口的实现
设计原则 违反 “合成复用原则” 符合 “合成复用原则”(推荐使用) 符合 “开闭原则”,接口扩展时只需修改适配器
核心目标 转换 Adaptee 接口为 Target 接口 转换 Adaptee 接口为 Target 接口 简化 Target 接口的实现,避免空方法冗余

6.1 使用场景

  • 类适配器:仅在需要重写被适配者的方法、且只适配一个被适配者时考虑使用(场景较少)。
  • 对象适配器:绝大多数场景下的首选,灵活性更高、耦合度更低,也是实际开发中最常用的适配器方式。
  • 缺省适配器
    • 当 Target 接口包含大量方法,但大部分实现类只需要其中少数几个方法时;
    • 用于简化复杂接口的实现,避免编写大量无意义的空方法(如 Java 的 MouseAdapterWindowAdapter)。
  • 三者共同适用
    • 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致;
    • 需要使用一个已经存在的类,但它的接口不符合我们的需求。

七、优缺点

7.1 优点

  • 解耦核心逻辑与适配逻辑适配器将客户端(调用方)和被适配者(Adaptee)隔离开,客户端只需面向统一的目标接口编程,无需关心被适配者的具体实现。即使被适配者的接口发生变化,你只需修改适配器,而不用改动核心业务代码,符合 “开闭原则”。
  • 复用已有功能,避免重复开发对于已有但接口不兼容的类(比如第三方库、遗留系统代码),无需重新编写功能逻辑,只需通过适配器转接就能复用,大大节省开发成本。
  • 灵活性高(对象适配器更突出)尤其是对象适配器,通过组合的方式可以灵活适配多个被适配者,甚至在运行时动态替换被适配者实例;而类适配器虽灵活性稍低,但也能通过重写被适配者方法实现定制化适配。

  • 透明化调用客户端使用适配器时,感知不到被适配者的存在,就像调用原生目标接口一样,降低了使用成本。

  • 缺省适配器

    • 彻底消除冗余的空方法,简化代码实现

    • 降低接口使用门槛,减少新手出错概率

    • 兼容接口扩展,降低维护成本

    • 支持 “按需实现”,适配不同场景的需求

7.2 缺点

  • 增加代码复杂度和理解成本引入适配器会新增额外的类(适配器类),如果系统中适配器过多,会让代码结构变得复杂,增加后期维护和理解的难度(比如新人接手时需要理清 “客户端→适配器→被适配者” 的调用链路)。

  • 可能带来性能损耗(可忽略)调用目标接口时,需要经过适配器的中转(调用适配器方法→适配器调用被适配者方法),相比直接调用被适配者方法多了一层调用,虽性能损耗极小(几乎不影响业务),但在极致高性能要求的场景下需要考虑。

  • 类适配器的局限性类适配器依赖继承,在 Java 等单继承语言中只能适配一个被适配者,且如果被适配者是最终类(final class),则无法通过类适配器实现适配;同时继承会提升类之间的耦合度,违反 “合成复用原则”。

  • 缺省适配器

    • 引入额外的中间类,增加代码层级

    • 可能隐藏 “未实现方法” 的潜在风险

    • 依赖继承,限制了类的扩展灵活性

    • 过度使用会导致 “适配器泛滥”

八、源码举例

8.1 JDK

8.1.1 目标接口

字符流接口,客户端期望的接口

public abstract class Reader implements Readable, Closeable
public abstract class Writer implements Appendable, Closeable, Flushable

8.1.2 适配者类

字节流接口,已有但接口不兼容

public abstract class InputStream implements Closeable
public abstract class OutputStream implements Closeable, Flushable

8.1.3 适配器类

将字节流适配成字符流

public class InputStreamReader extends Reader {

    private final StreamDecoder sd;

    /**
     * Creates an InputStreamReader that uses the
     * {@link Charset#defaultCharset() default charset}.
     *
     * @param  in   An InputStream
     *
     * @see Charset#defaultCharset()
     */
    public InputStreamReader(InputStream in) {
        super(in);
        sd = StreamDecoder.forInputStreamReader(in, this,
                Charset.defaultCharset()); // ## check lock object
    }
    ...
}
public class OutputStreamWriter extends Writer {

    private final StreamEncoder se;

    /**
     * Creates an OutputStreamWriter that uses the named charset.
     *
     * @param  out
     *         An OutputStream
     *
     * @param  charsetName
     *         The name of a supported
     *         {@link java.nio.charset.Charset charset}
     *
     * @throws     UnsupportedEncodingException
     *             If the named encoding is not supported
     */
    public OutputStreamWriter(OutputStream out, String charsetName)
        throws UnsupportedEncodingException
    {
        super(out);
        if (charsetName == null)
            throw new NullPointerException("charsetName");
        se = StreamEncoder.forOutputStreamWriter(out, this, charsetName);
    }
    ...
}

8.2 Spring

在 Spring AOP 的环绕通知(@Around)实现中,适配器模式(Adapter Pattern) 主要用于适配不同类型的通知接口到统一的执行逻辑,核心是让「不同类型的通知(比如前置、后置、环绕)」能通过统一的适配器接口被 Spring 的拦截器链执行

Spring AOP 的底层是基于 拦截器链(Interceptor Chain) 实现的:

  • 所有通知(Advice)最终都会被包装成 MethodInterceptor(方法拦截器),才能被拦截器链执行;
  • 环绕通知本身直接实现 MethodInterceptor,但前置(@Before)、后置(@After)等通知并不直接实现这个接口,需要适配器来转换;
  • 适配器模式在这里的核心作用:将不同类型的通知接口(如 MethodBeforeAdviceAfterReturningAdviceMethodInterceptor)适配成统一的 MethodInterceptor 接口,让拦截器链无需关心通知的具体类型,只需执行统一的接口方法。

8.2.1 目标接口

@FunctionalInterface
public interface MethodInterceptor extends Interceptor {
    @Nullable
    Object invoke(@Nonnull MethodInvocation invocation) throws Throwable;
}

8.2.2 适配者类

public interface MethodBeforeAdvice extends BeforeAdvice {
    void before(Method method, Object[] args, @Nullable Object target) throws Throwable;
}

public interface BeforeAdvice extends Advice {
}

8.2.3 适配器类

public interface AdvisorAdapter {
    boolean supportsAdvice(Advice advice);

    MethodInterceptor getInterceptor(Advisor advisor);
}
class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
    public boolean supportsAdvice(Advice advice) {
        return advice instanceof MethodBeforeAdvice;
    }

    public MethodInterceptor getInterceptor(Advisor advisor) {
        MethodBeforeAdvice advice = (MethodBeforeAdvice)advisor.getAdvice();
        return new MethodBeforeAdviceInterceptor(advice);
    }
}

九、其他相关设计模式

Java 设计模式・总结目录篇:从思想到代码实现-CSDN博客

    Logo

    汇聚全球AI编程工具,助力开发者即刻编程。

    更多推荐