结构型设计模式-全景分析

1 阅读39分钟

概述

在软件设计的世界里,如果说创建型模式关注的是“如何优雅地创造对象”,行为型模式聚焦于“对象之间如何高效协作”,那么结构型模式的核心使命便是——关注类与对象的组合,通过更灵活的结构设计获得更强大的功能。它们像建筑中的榫卯结构,不改变砖瓦的材质,却通过精巧的咬合方式搭建出宏伟殿堂。

结构型模式共有七种经典成员,根据其解决问题的倾向可快速归类为三大阵营:

  • 接口适配类:适配器模式(Adapter)、外观模式(Facade)—— 解决接口不匹配与系统复杂度问题
  • 对象组合增强类:装饰器模式(Decorator)、代理模式(Proxy)—— 在不修改原对象的基础上动态叠加新能力
  • 结构优化类:桥接模式(Bridge)、组合模式(Composite)、享元模式(Flyweight)—— 应对多维度变化、树形结构治理与资源复用挑战

本文旨在构建结构型模式的知识体系闭环:我们将从一张七模式核心对比矩阵切入,建立全局认知;继而深入15组极易混淆的模式两两对比,用代码层面的关键差异拨开迷雾;接着梳理三条清晰的演进路线图,理解模式从何而来、向何而去;然后深入Spring框架源码,洞察结构型模式在工业级产品中的协同舞蹈;再辅以基于问题驱动的选型决策树,让理论落地为工程判断力;在分布式与微服务场景下,我们将展示结构型模式如何支撑起API网关、服务网格、多租户数据源等现代架构设施;最后通过反模式警示、20道专家面试题以及跨类模式交叉应用,完成从记忆到驾驭的跃迁。

全文预计篇幅超过两万字,是结构型设计模式领域的一站式收官指南。


一、七种结构型模式核心对比

结构型模式的精髓在于利用“组合优于继承”的原则,但七种模式各自解决的问题域、应用场景和实现手法迥异。下表从六个关键维度对其进行全景式对比:

模式核心意图(一句话)解决的核心痛点关键角色数量类间关系特征JDK中的典型应用案例Spring框架中的体现
适配器模式将一个接口转换为客户期望的另一个接口接口不兼容,无法直接协作4(目标、源、适配器、客户端)继承或组合被适配者,实现目标接口InputStreamReader(字节流转字符流)HandlerAdapter(适配不同Controller)
桥接模式将抽象部分与实现部分分离,使它们可以独立变化多维度变化导致类爆炸5(抽象、精化抽象、实现、具体实现)抽象持有实现接口引用(组合)JDBC的DriverDriverManager架构AbstractApplicationContextConfigurableListableBeanFactory
组合模式将对象组合成树形结构以表示“部分-整体”层次对单个对象和组合对象需要统一处理方式3(组件、叶子、容器)容器与叶子实现同一接口(继承+组合)java.awt.ContainerComponentBeanDefinition的父子层级关系
装饰器模式动态地给一个对象添加一些额外的职责需要动态扩展功能,且避免子类爆炸4(抽象构件、具体构件、装饰抽象、具体装饰)装饰器继承抽象构件并组合构件引用java.io包(InputStream系列)Spring AOP中JdkDynamicAopProxy的链式调用
外观模式为子系统中的一组接口提供一个统一的高层接口子系统过于复杂,客户端调用困难2(外观类、子系统类集合)外观类依赖多个子系统类JdbcUtils(简化JDBC操作)DispatcherServlet(前端控制器)、JdbcTemplate
享元模式运用共享技术有效地支持大量细粒度对象大量相似对象造成内存开销过大4(享元接口、具体享元、非共享享元、工厂)工厂持有享元池,客户端持有外部状态Integer.valueOf()(-128~127缓存)Spring单例Bean的缓存池(DefaultSingletonBeanRegistry
代理模式为其他对象提供一种代理以控制对这个对象的访问直接访问对象不现实或需要额外控制逻辑3(抽象主题、真实主题、代理)代理与真实主题实现同一接口java.lang.reflect.Proxy(动态代理)@Transactional AOP代理、JdkDynamicAopProxy

表格深度解读:本质差异与适用边界

接口适配类(适配器 vs 外观)
适配器模式是一种“事后补救”模式,解决的是既定接口不匹配问题,关注点在于接口转换的粒度细且精准。例如,你的系统期望一个UserService接口,但第三方库只提供LegacyUserManager,适配器将后者包装成前者。外观模式则是一种“预先规划”的简化层,它不解决接口不匹配,而是解决接口数量过多带来的复杂度。外观模式的适用场景通常是子系统已经设计良好,只是调用链路繁琐。核心区别:适配器改变接口以匹配客户期望;外观提供更简单的接口以隐藏复杂性。

对象组合增强类(装饰器 vs 代理)
这是最易混淆的两个模式。装饰器强调功能的动态叠加,且叠加的顺序和组合是可变的,例如new BufferedReader(new InputStreamReader(new FileInputStream())),每一层都增强了功能。代理模式则强调访问控制,代理对象对被代理对象的控制通常是无差别的,它不改变被代理对象的主要行为,而是附加诸如权限校验、延迟加载、日志记录等横切关注点。Spring AOP中的@Transactional是典型的代理模式——我们并不希望“事务”这个行为可以像装饰器那样任意堆叠顺序,它是一层透明的控制壳。

结构优化类(桥接 vs 组合 vs 享元)
桥接模式应对的是多维度变化场景,其设计核心是识别出“抽象维度”和“实现维度”,并通过组合关系解耦。典型的例子是“消息类型(普通/紧急)”与“发送渠道(邮件/短信)”两个独立变化的维度。组合模式应对的是递归层次结构,它提供了一种透明地操作单个对象和组合对象的方式。享元模式应对的是海量相似对象的资源复用,核心是区分内部状态(可共享)与外部状态(不可共享)。一个常见的误区是将享元模式与对象池模式混淆:享元模式共享的是不可变的内在属性,而对象池(如数据库连接池)管理的是可变的、独占使用的对象实例


二、两两对比辨析矩阵

本节精选15组最易混淆的模式组合进行深度对比,每组均附关键代码差异示例。

1. 适配器模式 vs 装饰器模式

意图差异:适配器改变接口,装饰器不改变接口但增强功能。

代码示例

// 适配器模式:接口转换
interface MediaPlayer { void play(String audioType, String fileName); }
class Mp4Player { void playMp4(String fileName) { ... } }
class MediaAdapter implements MediaPlayer {
    private Mp4Player mp4Player = new Mp4Player();
    public void play(String audioType, String fileName) {
        if (audioType.equals("mp4")) mp4Player.playMp4(fileName);
    }
}

// 装饰器模式:接口不变,功能增强
interface Coffee { double cost(); }
class SimpleCoffee implements Coffee { public double cost() { return 5.0; } }
class MilkDecorator implements Coffee {
    private Coffee coffee;
    public MilkDecorator(Coffee coffee) { this.coffee = coffee; }
    public double cost() { return coffee.cost() + 1.5; }
}

关键区分点:适配器的MediaAdapter实现了新接口MediaPlayer,且其方法签名与被适配者Mp4Player完全不同。装饰器MilkDecorator与被装饰者实现相同接口Coffee,且其方法调用会委托给被装饰对象。

2. 装饰器模式 vs 代理模式

意图差异:装饰器注重功能动态组合,代理注重访问控制

代码示例

// 装饰器:功能可任意叠加顺序
DataInputStream dis = new DataInputStream(
                         new BufferedInputStream(
                             new FileInputStream("data.bin")));

// 代理:控制对真实对象的访问
class LazyImageProxy implements Image {
    private RealImage realImage;
    private String fileName;
    public void display() {
        if (realImage == null) realImage = new RealImage(fileName);
        realImage.display();
    }
}

关键区分点:装饰器通常在构造时由客户端显式嵌套,强调功能的组合灵活性;代理模式中,代理对象对真实对象的创建和访问时机拥有完全控制权,客户端甚至不知道真实对象的存在。

3. 代理模式 vs 适配器模式

意图差异:代理模式保持接口不变;适配器模式改变接口。

代码示例

// 代理模式:接口完全相同
interface Subject { void request(); }
class RealSubject implements Subject { ... }
class Proxy implements Subject {
    private RealSubject real;
    public void request() { /* access control */ real.request(); }
}

// 适配器模式:接口不同
interface Target { void operation(); }
class Adaptee { void specificOperation() { ... } }
class Adapter implements Target {
    private Adaptee adaptee;
    public void operation() { adaptee.specificOperation(); }
}

4. 桥接模式 vs 策略模式

意图差异:桥接模式属于结构型,分离抽象与实现两个固有维度;策略模式属于行为型,封装可互换的算法族

代码示例

// 桥接模式:抽象和实现是两个独立的层次结构
abstract class Shape { protected Color color; abstract void draw(); }
class Circle extends Shape { void draw() { color.applyColor(); } }
interface Color { void applyColor(); }
class RedColor implements Color { ... }

// 策略模式:策略是算法的不同实现
interface SortStrategy { void sort(int[] array); }
class Context { 
    private SortStrategy strategy;
    void executeStrategy(int[] arr) { strategy.sort(arr); }
}

关键区分点:桥接模式的客户端通常需要同时指定抽象和实现(如new Circle(new RedColor())),两个维度在对象创建时即组合,并在整个生命周期中通常不变。策略模式的策略可以在运行时随时替换,客户端只关心行为的选择。

5. 组合模式 vs 装饰器模式

意图差异:组合模式处理多对象聚合的树形结构;装饰器模式处理单个对象的动态增强

代码示例

// 组合模式:处理递归结构
interface Component { void operation(); }
class Leaf implements Component { ... }
class Composite implements Component {
    private List<Component> children = new ArrayList<>();
    public void operation() { for (Component c : children) c.operation(); }
}

// 装饰器模式:单链增强
class Decorator implements Component {
    protected Component component;
    public Decorator(Component c) { this.component = c; }
    public void operation() { component.operation(); /* extra logic */ }
}

6. 外观模式 vs 适配器模式

意图差异:外观简化多个接口的调用;适配器协调单个接口的不匹配。

代码示例

// 外观模式:封装子系统多个类
class HomeTheaterFacade {
    private Amplifier amp;
    private DvdPlayer dvd;
    public void watchMovie(String movie) {
        amp.on(); amp.setVolume(10); dvd.on(); dvd.play(movie);
    }
}

// 适配器模式:转换一个接口
class DvdPlayerAdapter implements MediaPlayer {
    private DvdPlayer dvd;
    public void play(String type, String file) { dvd.playDvd(file); }
}

7. 享元模式 vs 单例模式

意图差异:享元模式管理一组可共享的实例;单例模式确保类只有一个实例

代码示例

// 享元模式:工厂维护一个池
class CharFlyweightFactory {
    private Map<Character, CharFlyweight> pool = new HashMap<>();
    public CharFlyweight getFlyweight(char c) {
        return pool.computeIfAbsent(c, k -> new CharFlyweight(k));
    }
}

// 单例模式:仅有一个实例
class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() { return INSTANCE; }
}

8. 享元模式 vs 对象池模式

意图差异:享元共享不可变状态;对象池复用可变状态资源

代码示例

// 享元:内部状态固定,外部状态传入
class FontFlyweight { // 字体名称、大小固定
    private final String fontName;
    public void render(String text, int x, int y) { /* 外部状态x,y */ }
}

// 对象池:对象状态可变,需要重置
class ConnectionPool {
    private BlockingQueue<Connection> pool;
    public Connection borrowConnection() { ... }
    public void returnConnection(Connection conn) { /* 重置conn状态 */ }
}

9. 代理模式 vs 外观模式

意图差异:代理模式关注单个对象的访问控制;外观模式关注子系统多个对象的简化

10. 桥接模式 vs 适配器模式

意图差异:桥接在设计阶段分离抽象与实现;适配器在事后协调已有接口。

11. 组合模式 vs 享元模式

意图差异:组合管理结构性层次;享元管理实例数量

12. 装饰器模式 vs 责任链模式

意图差异:装饰器增强单一处理路径,责任链传递请求直到被处理。责任链中的节点可以中断请求,装饰器则一定会传递。

13. 外观模式 vs 中介者模式

意图差异:外观是单向依赖(客户端→外观→子系统),中介者是多向协作(同事类→中介者→同事类)。

14. 适配器模式 vs 门面模式

门面模式(Facade)与外观模式为同一概念,与适配器的区别同6。

15. 代理模式 vs 桥接模式

意图差异:代理模式中代理与实体实现同一接口,结构简单;桥接模式中抽象与实现分属不同接口层次,结构更复杂。


结构型模式三维分布轴线图

下图以“接口转换—对象增强—结构优化”三个维度展示七种模式的分布关系:

flowchart TD
    subgraph Dimension["三大核心维度"]
        A[接口转换维度] --> A1[适配器模式]
        A --> A2[外观模式]
        B[对象增强维度] --> B1[装饰器模式]
        B --> B2[代理模式]
        C[结构优化维度] --> C1[桥接模式]
        C --> C2[组合模式]
        C --> C3[享元模式]
    end
    
    A1 -- "改变接口但保持功能" --> Core[结构型模式核心<br>组合优于继承]
    A2 -- "简化接口调用复杂度" --> Core
    B1 -- "动态叠加新职责" --> Core
    B2 -- "透明控制对象访问" --> Core
    C1 -- "分离多维度变化" --> Core
    C2 -- "统一树形结构处理" --> Core
    C3 -- "共享细粒度对象" --> Core

文字说明
此流程图将七种模式定位在三个主要功能轴线上。接口转换轴线上的模式(适配器、外观)侧重于解决客户端与现有代码库之间的集成摩擦——适配器是点对点的接口翻译,外观是点对面的门面简化。对象增强轴线上的模式(装饰器、代理)均在保持原对象接口不变的前提下植入新逻辑,但装饰器关注功能的可组合性与顺序无关的增强,代理则关注访问的时机、权限与位置控制。结构优化轴线上的模式(桥接、组合、享元)面向的是系统架构层面的解耦与资源效率——桥接将抽象与实现两个维度分离以避免类数量乘积爆炸,组合用统一的操作逻辑消解了客户端对部分与整体的区别对待,享元则通过共享池化技术在内存层面实现了数量的集约。理解这三条轴线,便抓住了结构型模式的战略定位。


三、结构型模式的演进路线图

设计模式并非凭空诞生,它们是从软件工程实践中归纳出的、对抗复杂性与僵化性的演进产物。本节按三条脉络梳理结构型模式的演进逻辑。

1. 接口兼容演进线

flowchart LR
    A[硬编码兼容<br>大量if-else/类型判断] --> B[适配器模式<br>单向适配]
    B --> C[外观模式<br>简化多接口调用]
    B --> D[双向适配器<br>实现双向透明转换]
    
    A -- "痛点:代码耦合、难以扩展" --> B
    B -- "痛点:多子系统接口仍复杂" --> C
    B -- "痛点:需要双向互操作" --> D

详细讲解
早期系统集成中,程序员直接在新业务逻辑中嵌入对旧系统API的调用,并伴生大量条件分支进行数据类型转换,这就是“硬编码兼容”阶段。当需要集成的外部系统增多时,适配器模式将转换逻辑封装进独立类,实现了单向的接口标准化。例如,一个报表系统需要同时从MySQL、MongoDB和外部REST API获取数据,可以分别为三者编写适配器,实现统一的ReportDataFetcher接口。

随着适配器增多,客户端需要同时与多个适配器交互,调用链路变得复杂。此时外观模式应运而生——它提供一个更高级别的统一入口,内部协调多个适配器(或子系统)的协作。例如,一个ReportFacade内部组合了MySQLAdapterMongoAdapterRestAdapter,对外只暴露generateReport()方法。

在某些对称场景下(如对象-XML互转),双向适配器提供toXml()fromXml()两个方向的方法,实现无感知的双向转换。演进的核心推动力始终是隔离变化、简化客户端视角

2. 功能增强演进线

flowchart LR
    A[继承扩展<br>子类重写方法] --> B[装饰器模式<br>动态组合功能]
    B --> C[代理模式<br>访问控制与增强]
    C --> D[AOP<br>横切关注点泛化]
    
    A -- "痛点:类爆炸、静态编译期绑定" --> B
    B -- "痛点:功能增强需更透明的控制" --> C
    C -- "痛点:分散的代理逻辑需集中管理" --> D

详细讲解
继承是实现功能扩展最直接的手段,但它是静态的,且容易导致“组合爆炸”(如为InputStream的每一种功能组合都编写一个子类)。装饰器模式通过“组合+委托”将功能的叠加推迟到运行时,并允许任意顺序组合。Java I/O库是装饰器模式的教科书级应用。

然而装饰器的增强是“显式”的——客户端必须主动创建装饰链。当我们需要对一组类施加统一的、隐式的增强逻辑(如日志、事务、权限)时,代理模式提供了更合适的抽象。代理对象对客户端是透明的,它接管了对象访问的入口,可以在不修改客户端代码的情况下植入控制逻辑。

当代理逻辑大量重复出现时,AOP(面向切面编程)作为代理模式的泛化,将横切关注点模块化为“切面”,由框架在运行时动态织入。Spring AOP正是基于动态代理技术(JDK Proxy或CGLIB)实现了这一演进顶点。

3. 结构优化演进线

flowchart LR
    A[多继承/多层继承<br>类数量随维度乘积增长] --> B[桥接模式<br>分离抽象与实现维度]
    B --> C[组合模式<br>递归树形结构统一处理]
    C --> D[享元模式<br>共享池化减少实例数量]
    
    A -- "痛点:类爆炸与强耦合" --> B
    B -- "痛点:部分-整体需差异化处理" --> C
    C -- "痛点:大量相似对象占用内存" --> D

详细讲解
当系统存在两个或多个独立变化的维度时,使用继承会导致类数量呈M×N增长(如RedCircleRedSquareBlueCircleBlueSquare...)。桥接模式通过将“抽象”和“实现”分离为两个独立的类层次,并用组合关系连接,将类总数降至M+N。JDBC的DriverConnection体系是典型例子。

组合模式解决了层次结构中的“递归统一”问题。文件系统中的目录和文件,GUI中的容器和组件,都需要对单个对象和组合对象一视同仁。组合模式通过让叶子节点和容器节点实现相同接口,实现了操作的透明性。

当组合模式中的叶子节点数量达到海量级别(如文本编辑器中的字符对象),内存占用成为瓶颈。享元模式引入共享池,将字符对象的内在属性(如字体、字号)抽取为可共享的享元,仅将位置等外部状态保留在客户端。演进至此,完成了从“结构清晰”到“性能极致”的优化闭环。


四、框架源码中的结构型模式协同应用

Spring框架是结构型模式协同工作的典范,本节深入分析其核心模块中的模式交织。

1. Spring AOP中的代理模式与装饰器模式

Spring AOP的核心是代理模式,但JdkDynamicAopProxy的调用链设计又融合了装饰器模式的思想。

// JdkDynamicAopProxy 源码片段(简化)
public Object invoke(Object proxy, Method method, Object[] args) {
    // 获取拦截器链(Advice/Interceptor的装饰器链)
    List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
    if (chain.isEmpty()) {
        return method.invoke(target, args);
    } else {
        // 构造调用链,每个拦截器像装饰器一样包裹目标方法
        ReflectiveMethodInvocation invocation = 
            new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
        return invocation.proceed();
    }
}

边界分析:此处代理模式体现在JdkDynamicAopProxy作为InvocationHandler,对客户端隐藏了真实target的存在。装饰器模式体现在拦截器链的组织方式:MethodBeforeAdviceInterceptorAfterReturningAdviceInterceptor等像装饰器一样层层包裹目标方法调用,每一层添加特定增强逻辑。关键区别:代理模式决定“是否”以及“何时”调用目标;装饰器模式决定“增强什么”以及“以什么顺序增强”。Spring AOP的代理对象是入口守卫,而内部的拦截器链则是功能装饰的载体。

2. Spring MVC中的适配器模式与外观模式

DispatcherServlet作为前端控制器,是外观模式的典型应用——它屏蔽了内部HandlerMappingHandlerAdapterViewResolver等组件的复杂性,对外提供简单的HTTP请求处理接口。

// DispatcherServlet 核心处理逻辑
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {
    // 1. 根据请求找到Handler
    HandlerExecutionChain mappedHandler = getHandler(request);
    // 2. 根据Handler找到适配器(适配器模式关键点)
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    // 3. 执行Handler,返回ModelAndView
    ModelAndView mv = ha.handle(request, response, mappedHandler.getHandler());
    // 4. 视图渲染...
}

适配器模式的应用:Spring MVC支持多种Controller类型(@Controller注解、HttpRequestHandler、旧式Controller接口等),HandlerAdapter接口充当适配器角色:

public interface HandlerAdapter {
    boolean supports(Object handler);
    ModelAndView handle(HttpServletRequest req, HttpServletResponse res, Object handler);
}

每种Controller类型对应一个适配器实现(RequestMappingHandlerAdapterHttpRequestHandlerAdapter等)。DispatcherServlet通过getHandlerAdapter()遍历适配器列表,找到支持当前Handler的适配器,从而统一了不同Handler的调用方式。

3. Spring IoC中的组合模式与享元模式

组合模式BeanDefinition具有父子层级关系,形成树形结构。一个BeanDefinition可以通过parentName指定父定义,子定义继承父定义的属性。这实现了配置信息的组合复用。

public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor {
    private String parentName;
    // 获取父定义合并后的属性
    public abstract AbstractBeanDefinition getOriginatingBeanDefinition();
}

享元模式:Spring的单例Bean容器本质是一个巨大的享元工厂。DefaultSingletonBeanRegistry内部使用Map<String, Object> singletonObjects缓存单例Bean。对于@Scope("singleton")的Bean,Spring在首次创建后将其放入缓存,后续请求直接返回共享实例,这正是享元模式“内部状态共享”的体现。

4. Spring事务管理中的代理模式

@Transactional注解的背后是TransactionInterceptor作为AOP通知,与代理对象配合工作:

// TransactionInterceptor 核心逻辑
public Object invoke(MethodInvocation invocation) throws Throwable {
    // 获取事务属性
    TransactionAttribute txAttr = ...;
    // 获取事务管理器
    PlatformTransactionManager tm = determineTransactionManager(txAttr);
    // 开启事务(如果需要)
    TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
    Object retVal;
    try {
        retVal = invocation.proceed(); // 执行目标方法
    } catch (Throwable ex) {
        completeTransactionAfterThrowing(txInfo, ex); // 回滚
        throw ex;
    } finally {
        cleanupTransactionInfo(txInfo);
    }
    commitTransactionAfterReturning(txInfo); // 提交
    return retVal;
}

这里代理模式保证了事务控制代码对业务代码的透明性。业务Service无需感知事务的存在,完全由代理对象在调用前后进行事务边界管理。

5. Spring MVC请求处理时序图

sequenceDiagram
    participant Client as 客户端
    participant DS as DispatcherServlet<br/>(外观模式)
    participant HM as HandlerMapping
    participant HA as HandlerAdapter<br/>(适配器模式)
    participant C as Controller
    participant VR as ViewResolver
    participant V as View
    
    Client->>DS: HTTP请求
    DS->>HM: getHandler(request)
    HM-->>DS: HandlerExecutionChain
    DS->>HA: getHandlerAdapter(handler)
    HA-->>DS: HandlerAdapter实例
    DS->>HA: handle(request, response, handler)
    HA->>C: 调用Controller方法
    C-->>HA: ModelAndView
    HA-->>DS: ModelAndView
    DS->>VR: resolveViewName(viewName)
    VR-->>DS: View对象
    DS->>V: render(model, request, response)
    V-->>Client: HTTP响应

文字说明
此图完整呈现了Spring MVC处理一次HTTP请求时结构型模式的协同。DispatcherServlet作为外观模式的统一入口,封装了请求处理的整个复杂流程,使客户端只需与这一个Servlet交互。当请求抵达后,HandlerMapping负责路由决策,但真正的模式亮点在于HandlerAdapter适配器模式应用——无论Controller是哪种具体类型(注解驱动、实现特定接口等),DispatcherServlet都通过适配器接口HandlerAdapter以统一方式调用。这种设计使得Spring MVC可以无限扩展新的Controller类型而无需修改核心调度逻辑。此外,HandlerExecutionChain中的拦截器(Interceptor)本质上是代理模式与装饰器模式的混合体,它们在目标Controller执行前后进行横向增强。整个流程中,组合模式体现在ModelAndView可以包含嵌套的Model数据树,享元模式则隐式作用于View实例的缓存(如JSP视图对象复用)。这一套结构型模式的精密配合,成就了Spring MVC的灵活与强大。


五、结构型模式的选型决策指南

面对具体问题,如何快速锁定合适的结构型模式?本节提供基于问题特征的决策树与量化参考。

决策流程图

flowchart TD
    Start["遇到结构设计问题"] --> Q1{"接口不兼容?"}
    Q1 -->|"是"| Q1a{"涉及多个子系统的复杂调用?"}
    Q1a -->|"是"| Facade["外观模式"]
    Q1a -->|"否"| Adapter["适配器模式"]
    
    Q1 -->|"否"| Q2{"需要动态增强对象功能?"}
    Q2 -->|"是"| Q2a{"增强需对客户端透明 且侧重访问控制?"}
    Q2a -->|"是"| Proxy["代理模式"]
    Q2a -->|"否"| Decorator["装饰器模式"]
    
    Q2 -->|"否"| Q3{"存在多个独立变化的维度?"}
    Q3 -->|"是"| Bridge["桥接模式"]
    Q3 -->|"否"| Q4{"对象形成部分-整体层次结构?"}
    Q4 -->|"是"| Composite["组合模式"]
    Q4 -->|"否"| Q5{"大量细粒度对象导致内存压力?"}
    Q5 -->|"是"| Flyweight["享元模式"]
    Q5 -->|"否"| Reconsider["重新分析问题域"]

使用说明
此决策树遵循“按问题特征逐层过滤”的原则。第一层判断接口兼容性,这是结构型模式最外显的应用信号。如果问题是集成老旧系统或第三方SDK,且接口定义与现有代码不一致,走适配器分支;若子系统众多且调用复杂,外观模式能有效降低客户端负担。第二层判断功能增强的形态:装饰器适用于显式的、可组合的功能叠加(如I/O流),代理适用于隐式的、控制性的能力植入(如延迟加载、权限校验)。第三层处理架构层面的多维变化,桥接模式是解耦利器。第四层的组合模式是处理递归树形结构的不二法门。第五层的享元模式专攻性能优化,但务必确认对象存在可共享的内部状态。若所有问题特征均不匹配,建议回到问题域重新审视——可能根本不需要结构型模式,或者问题是行为型/创建型模式的范畴。

技术选型量化参考指标

模式适用场景关键词典型技术案例
适配器模式遗留系统集成、第三方SDK封装、多数据源统一接口SLF4J日志门面、Apache Camel类型转换器
桥接模式多维度独立变化、运行时切换实现、驱动架构设计JDBC Driver体系、Selenium WebDriver
组合模式树形结构、部分-整体关系、递归处理文件系统、JSON/XML节点处理、UI组件树
装饰器模式运行时动态组合功能、避免子类爆炸、I/O处理Java I/O流、Collections.synchronizedList等包装器
外观模式简化复杂子系统调用、分层解耦、为外部提供统一APISLF4J(再次)、Spring JdbcTemplate
享元模式大量细粒度对象、内部状态可共享、缓存优化Integer缓存、字符串常量池、数据库连接池(类享元思想)
代理模式访问控制、延迟加载、远程透明调用、日志与监控Spring AOP、RPC客户端Stub、Hibernate Lazy Loading

六、分布式场景下的结构型模式综合应用

微服务与云原生时代,结构型模式在分布式基础设施层发挥着基石作用。

1. API网关中的外观模式与代理模式

API网关(如Spring Cloud Gateway、Kong)是外观模式的分布式表达。它将内部数十甚至上百个微服务的复杂拓扑隐藏起来,对外提供统一、简洁的RESTful或GraphQL端点。同时,网关也扮演了代理模式的角色——客户端的请求被网关代理转发至后端服务,期间可附加认证、限流、日志等控制逻辑。

// Spring Cloud Gateway 路由配置示例
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("order_service", r -> r.path("/api/orders/**")
            .filters(f -> f.addRequestHeader("X-Source", "gateway"))
            .uri("lb://ORDER-SERVICE")) // 代理转发
        .build();
}

2. 服务网格Sidecar中的代理模式

服务网格(如Istio)通过Sidecar代理(Envoy)接管微服务间所有流量,这是代理模式的极致应用。业务容器完全无需感知服务发现、负载均衡、mTLS等网络细节,Envoy作为透明代理处理一切。

# Istio Sidecar注入后,Pod内包含两个容器:
# - 业务容器: my-app
# - Envoy代理: istio-proxy
# 所有进出my-app的流量都被iptables规则劫持至Envoy处理

3. 分布式缓存中的享元模式与代理模式

客户端缓存(如Redis Client的本地缓存)利用享元模式减少网络开销。例如,Caffeine或Guava Cache在JVM堆内缓存热点数据,本质上是对远端数据对象的一个“共享池”。同时,缓存客户端Proxy(如JedisProxy)负责路由、分片、连接池管理,是代理模式的体现。

4. 多云适配中的适配器模式与桥接模式

多云管理平台需要统一操作AWS、Azure、GCP的对象存储。适配器模式为每个云厂商编写适配器,实现统一的CloudStorage接口。桥接模式进一步将“存储操作抽象”(如上传、下载)与“云厂商实现”分离,支持运行时切换云平台。

interface CloudStorage { void upload(String bucket, File file); }
class S3Adapter implements CloudStorage { ... }
class AzureBlobAdapter implements CloudStorage { ... }

// 桥接模式部分:抽象与实现分离
abstract class StorageService { 
    protected CloudStorage storage;
    abstract void backup(String path); 
}
class DatabaseBackupService extends StorageService { ... }

5. 微服务聚合层中的外观模式与组合模式

一个商品详情页可能聚合了商品基本信息、库存、评论、推荐等多个微服务的数据。聚合层(或称BFF,Backend for Frontend)使用外观模式简化前端调用,内部并行或串行调用多个服务,并将结果组合成前端所需的视图模型。如果返回数据本身具有树形结构(如商品包含SKU列表,SKU包含属性),则组合模式可被用于构建响应数据结构。

6. RPC框架中的代理模式与装饰器模式

Dubbo等RPC框架中,客户端通过ReferenceConfig获取一个服务接口的代理对象。该代理对象封装了服务发现、协议编解码、网络传输、负载均衡等复杂逻辑,使得调用远程服务如同调用本地方法。

// 代理模式:Consumer端看到的只是接口代理
UserService userService = DubboReferenceBuilder.build(UserService.class);
User user = userService.getUserById(1L); // 透明远程调用

框架内部的Filter链(如MonitorFilterTraceFilter)则运用装饰器模式,在请求发送前后动态织入监控、追踪等增强逻辑。

7. 综合案例:多租户数据源路由框架设计

需求背景:SaaS系统需要根据租户ID动态切换数据源,同时支持多种数据库(MySQL、PostgreSQL)和读写分离。

核心设计

  • 代理模式RoutingDataSource作为javax.sql.DataSource的代理,根据上下文租户ID返回真实数据源。
  • 适配器模式MySQLDialectPgDialect适配不同数据库的分页、函数差异。
  • 享元模式:数据库连接池(HikariCP)内部维护连接对象的复用池。
  • 装饰器模式:读写分离通过ReadWriteSplittingDataSourceDecorator增强,对getConnection()方法进行路由装饰。

核心代码骨架

// 1. 代理模式:路由数据源
public class TenantAwareRoutingDataSource implements DataSource {
    private Map<String, DataSource> tenantDataSources;
    @Override
    public Connection getConnection() {
        String tenantId = TenantContext.getCurrentTenant();
        DataSource ds = tenantDataSources.get(tenantId);
        return ds.getConnection();
    }
    // 其他方法委托...
}

// 2. 适配器模式:方言适配
interface SqlDialect { String getPaginationSql(String sql, int offset, int limit); }
class MySQLDialect implements SqlDialect { ... }
class PostgresDialect implements SqlDialect { ... }

// 3. 装饰器模式:读写分离增强
class ReadWriteSplittingDataSourceDecorator implements DataSource {
    private DataSource writeDs;
    private List<DataSource> readDsList;
    private LoadBalancer lb;
    @Override
    public Connection getConnection() {
        if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
            return lb.choose(readDsList).getConnection();
        }
        return writeDs.getConnection();
    }
}

// 4. 享元模式:连接池(HikariCP内部实现,此处略)

Mermaid架构图

flowchart TD
    subgraph Client
        A[业务代码<br>调用JdbcTemplate]
    end
    
    subgraph Proxy_Layer["代理层"]
        B[TenantAwareRoutingDataSource<br>代理模式]
        C[ReadWriteSplittingDataSourceDecorator<br>装饰器模式]
    end
    
    subgraph Adapter_Layer["适配层"]
        D[MySQLDialect<br>适配器模式]
        E[PgDialect<br>适配器模式]
    end
    
    subgraph Flyweight_Layer["享元层"]
        F[HikariCP连接池<br>享元模式]
    end
    
    subgraph Physical["物理层"]
        G[(TenantA_MySQL)]
        H[(TenantB_PostgreSQL)]
    end
    
    A --> B
    B --> C
    C -- "SQL方言适配" --> D
    C -- "SQL方言适配" --> E
    D --> F
    E --> F
    F --> G
    F --> H

文字说明
本综合案例展示了结构型模式在解决“多租户数据源路由”这一复杂工程问题时的协同价值。在代理层TenantAwareRoutingDataSource扮演了代理模式中的Proxy角色。它实现了标准DataSource接口,对上层业务代码完全透明,业务层只需像使用普通数据源一样获取连接,而租户路由逻辑完全由代理类接管。这里的关键是代理模式对“访问控制”的增强——它决定了哪个租户应该连接到哪个物理数据源。

为了支持读写分离,我们在代理层之上叠加了装饰器模式ReadWriteSplittingDataSourceDecorator同样实现DataSource接口,它内部组合了真正的TenantAwareRoutingDataSource(或具体的写库/读库数据源)。在getConnection()方法中,装饰器通过检查Spring事务的只读标志来决定返回读库还是写库的连接。装饰器的优势在于,读写分离逻辑可以独立于租户路由逻辑开发和测试,且可以灵活插拔(如果某些租户不需要读写分离,可移除此装饰器)。

当连接被获取后,业务代码可能会执行特定数据库的SQL。由于不同租户可能使用不同数据库产品,适配器模式在此发挥作用。SqlDialect接口定义了一套标准的分页、函数等方言操作,MySQLDialectPostgresDialect分别实现。业务代码通过注入的SqlDialect来生成SQL片段,从而避免编写if (dbType == "mysql")这类硬编码。

最后,每个具体的数据源背后都是HikariCP连接池,连接池本身是享元模式的杰出实践。HikariCP维护着一组已建立的物理连接(内部状态:URL、用户名、密码;外部状态:是否被借用、最后使用时间)。当业务请求getConnection()时,池化技术复用已存在的连接对象,避免了频繁创建销毁TCP连接的开销。

这四种结构型模式各司其职:代理负责路由决策,装饰器负责功能增强,适配器负责差异抹平,享元负责资源复用。它们形成的设计链条,使得整个多租户数据源架构既具备强大的扩展性,又保持了业务代码的简洁与清晰。


七、常见设计误区与反模式警示

模式是双刃剑,滥用或误用将导致代码腐化。本节警示七种典型陷阱。

1. 适配器模式的滥用:过度适配

误区:每遇到接口不匹配就引入适配器,导致系统充满A2BAdapterB2CAdapter,最终调用链冗长难懂。 重构建议:如果被适配的代码是团队可控的,应直接修改不兼容接口使其统一,而非无止境堆积适配器。适配器应主要用于无法修改的外部系统集成。

// 反模式:自己的代码却用适配器包装
class MyOldService { void oldMethod(){} }
class MyOldServiceAdapter implements NewInterface { ... } 

// 改进:直接重构MyOldService实现NewInterface
class MyOldService implements NewInterface {
    void newMethod() { this.oldMethod(); } // 委托给旧方法过渡
}

2. 装饰器模式的滥用:装饰过深与职责混淆

误区:装饰器链超过5层,调试时难以定位实际执行者;将代理职责(如权限校验)用装饰器实现,导致装饰器链顺序依赖敏感。 重构建议:保持装饰链简洁(通常不超过3层);明确区分装饰器(功能增强)与代理(访问控制)。引入Builder模式辅助构建装饰器链,提高可读性。

// 反模式:装饰器承担权限检查(职责错位)
class AuthInputStream extends FilterInputStream {
    public int read() { if(hasPermission) return super.read(); }
}
// 改进:权限检查应使用代理模式,在访问前统一拦截

3. 代理模式的滥用:静态代理类爆炸

误区:为每个需要增强的类手写静态代理类,导致类数量翻倍。 重构建议:立即转向动态代理技术(JDK Proxy或CGLIB),或使用AOP框架统一管理。静态代理仅适用于接口稳定且增强逻辑极简单的场景。

4. 外观模式的滥用:上帝外观类

误区:单个外观类承担了过多职责,包含了上百个方法,成为系统瓶颈和修改热点。 重构建议:按业务领域拆分为多个细粒度外观类(如OrderFacadeUserFacade)。外观类应保持“瘦身”,仅负责协调,不包含具体业务逻辑。

5. 享元模式的滥用:线程安全问题与状态外泄

误区:将可变状态放入享元对象,导致多线程环境下的数据错乱;或者外部状态管理散落在客户端各处,难以维护。 重构建议:严格保证享元对象不可变(final字段、无setter)。使用享元工厂集中管理外部状态的计算与传递。对于需要线程安全的场景,优先考虑不可变对象或ThreadLocal封装外部状态。

// 反模式:享元包含可变状态
class Font { private String name; public void setName(String n){...} } // 危险!

// 改进:享元完全不可变
final class Font { private final String name; public Font(String name){ this.name=name; } }

6. 组合模式的滥用:叶子节点被迫实现无用方法

误区:为了让叶子节点实现Component接口,必须实现add()remove()等容器方法,抛出UnsupportedOperationException重构建议:采用安全组合模式——仅在Composite类中提供容器管理方法,Component接口只定义通用操作。客户端在使用容器方法前需进行类型判断。

// 透明组合(有滥用风险)
interface Component { void add(Component c); } // Leaf被迫实现抛异常

// 安全组合(推荐)
interface Graphic { void draw(); }
class Picture implements Graphic {
    public void add(Graphic g) { ... } // 仅在Composite中提供
}

7. 桥接模式的滥用:过度设计

误区:系统中虽然有两个变化维度,但其中一个维度几乎不会变化或变化概率极低,却强行使用桥接模式增加复杂度。 重构建议:遵循YAGNI原则。仅在两个维度都确定会独立变化,且变化频率较高时,才引入桥接模式。否则,简单的继承或策略模式可能更经济。


八、面试题精选与专家级解答

1. 适配器、装饰器、代理在结构上非常相似,如何从意图上区分?
:从三个维度区分:① 接口变化:适配器改变接口,装饰器和代理不改变接口。② 关系构建:适配器和代理通常与被适配/被代理对象是一对一关系;装饰器可以一对多嵌套。③ 关注点:适配器关注兼容性;装饰器关注功能增强的动态组合;代理关注访问控制

2. 桥接模式和策略模式的核心区别?结合JDBC分析。
:桥接模式是结构型,解决类结构设计问题,抽象与实现在对象构建时组合;策略模式是行为型,解决算法互换问题,策略可在运行时动态切换。JDBC中,DriverManagerDriver是桥接关系——DriverManager是抽象,持有Driver实现引用,两者在设计时分离。而ConnectionsetAutoCommit()可视为策略模式,改变事务行为。

3. 为什么说组合模式是“透明”的?透明组合与安全组合的区别?
:“透明”指客户端可以一致地对待叶子和容器,无需区分类型。透明组合将容器管理方法(add/remove)定义在Component接口中,叶子实现为抛异常;安全组合仅在Composite类中定义容器方法,客户端使用时需类型判断。透明组合牺牲部分安全性换取便利性。

4. 享元模式的内部状态和外部状态如何区分?结合Integer.valueOf分析。
:内部状态是可共享的、不随环境改变的属性(如Integer的数值-128~127);外部状态是不可共享的、随场景变化的属性(如Integer作为Map的key时的上下文关系)。Integer.valueOf(127)从缓存池返回共享对象,其内部状态为127,外部状态由客户端代码持有。

5. 外观模式和门面模式是同一概念吗?与适配器的本质不同?
:外观模式即门面模式,英文均为Facade。与适配器的本质不同:外观简化接口,适配器转换接口。外观面对的是一个子系统,适配器面对的是单个类的接口。

6. Spring AOP同时体现代理模式和装饰器模式,如何共存?
:代理模式体现在AopProxy对目标对象的包装,负责控制何时调用目标;装饰器模式体现在拦截器链MethodInterceptor的层层嵌套,每一层负责增加什么逻辑。两者协同:代理提供了植入装饰链的入口,装饰链完成了具体的增强行为。

7. JDK动态代理和CGLIB代理在结构型模式中的优劣?
:JDK动态代理基于接口,与被代理类是兄弟关系(都实现接口),符合组合优于继承;但要求必须有接口。CGLIB基于继承,与被代理类是父子关系,可代理任何非final类;但无法代理final方法,且生成子类可能对设计有侵入。结构型模式倾向于使用基于接口的JDK代理,更符合接口隔离原则。

8. MyBatis的Mapper代理、Plugin代理、ConnectionLogger代理各属哪种?
MapperProxy代理模式,为DAO接口生成代理实现,隐藏SQL执行细节。Plugin(拦截器)是装饰器模式与责任链模式混合,通过层层包装Executor等核心对象增强功能。ConnectionLogger代理模式,为JDBC连接创建代理以输出日志,属于访问控制与监控增强。

9. 在微服务架构中,如何利用外观和适配器设计统一多云SDK?
:外观模式构建CloudStorageFacade,提供upload()download()等统一方法,内部聚合不同云厂商的SDK。适配器模式为每个厂商编写S3AdapterOssAdapter,它们实现CloudStorage接口,负责将统一调用翻译为厂商SDK特有调用。两者结合使业务代码完全屏蔽多云差异。

10. 装饰器和代理都用于增强,为何说代理更侧重“控制”?
:装饰器的增强是附加性的,如增加缓冲、压缩功能,这些功能是对象能力的一部分,客户端期望获得这些增强。代理的增强是限制性保障性的,如权限验证、延迟加载,这些功能不是对象原有能力,而是访问前提,客户端可能无感知。代理模式的核心语义是“代替”,意味着它有权拒绝或延迟对真实对象的访问。

11. 组合模式处理树形结构时如何与迭代器模式、访问者模式配合?
:组合模式提供统一接口iterator()返回迭代器,迭代器模式负责遍历策略(深度优先/广度优先)。访问者模式通过accept(Visitor v)方法,将操作逻辑从树结构中分离。组合模式是“形”,迭代器是“游标”,访问者是“操作”。

12. 享元模式与对象池模式的本质区别?数据库连接池属哪种?
:享元模式中对象不可变且可同时共享;对象池中对象可变且互斥借用。数据库连接池属于对象池模式,因为每个连接有独立的事务状态、会话变量,不能同时被多个客户端共享。

13. 桥接模式中的“抽象”和“实现”与日常说的抽象类和实现类有何区别?
:桥接模式的“抽象”指高层的、与业务逻辑相关的控制部分;“实现”指底层的、与具体平台相关的操作部分。日常的抽象类是abstract class,实现类是class Impl,这是语法层面。桥接模式强调的是概念层次的分离,抽象与实现可分别演化。

14. 如何用结构型模式设计支持运行时切换缓存策略的框架?
:使用桥接模式分离“缓存操作抽象”(get/put)与“缓存实现”(Redis/Caffeine)。使用代理模式为缓存客户端增加监控、降级功能。使用装饰器模式支持缓存功能的多层嵌套(如本地缓存+远程缓存)。

15. 代理模式在分布式RPC框架中的角色?以Dubbo为例。
:在Dubbo中,服务消费者通过ReferenceConfig获得服务接口的远程代理对象。该代理对象实现了服务接口,内部封装了服务发现、协议编解码、网络传输等逻辑。消费者调用本地接口方法,代理对象将其转换为RPC请求,透明化远程调用细节。这是典型的远程代理(Remote Proxy)。

16. 装饰器模式在Java IO体系中的设计优劣?
优势:极致灵活,可任意组合功能,符合开闭原则。劣势:类数量多(装饰器子类),新手学习曲线陡峭,调试嵌套流困难。但总体利大于弊,是组合优于继承的典范。

17. 结构型模式中哪些是类模式,哪些是对象模式?
类模式仅有适配器模式(多重继承实现时),其余均为对象模式。类模式基于继承,编译时确定;对象模式基于组合,运行时动态确定。GoF推崇对象模式,因组合比继承更灵活。

18. 享元模式如何与单例模式结合?以Spring单例Bean为例。
:Spring的DefaultSingletonBeanRegistry使用单例模式管理容器自身实例,但其内部缓存Map存放的Bean是享元模式的体现。多个消费者获取同一个单例Bean时,返回的是共享的享元对象。单例模式确保工厂唯一,享元模式确保产品共享。

19. 在DDD中,结构型模式如何用于防腐层设计?
:防腐层(ACL)的核心是适配器模式外观模式的组合。外观模式为外部系统提供统一、简化的接口;适配器模式将外部系统的模型转换为限界上下文内部的领域模型。桥接模式可用于分离领域抽象与外部仓储实现。

20. 哪些模式适合框架层面,哪些适合业务层面?
框架层面:代理模式(AOP、RPC)、适配器模式(日志门面)、桥接模式(SPI扩展点)、享元模式(连接池、常量池)。业务层面:组合模式(组织架构树、物料清单)、装饰器模式(订单优惠计算)、外观模式(对外暴露的服务聚合层)。


九、结构型模式与创建型、行为型模式的交叉应用

1. 代理模式 + 单例模式

全局唯一的代理管理器,确保所有代理请求通过统一入口。

public enum ProxyManager {
    INSTANCE;
    private Map<Class<?>, Object> proxyCache = new ConcurrentHashMap<>();
    public <T> T getProxy(Class<T> clazz) {
        // 动态代理创建逻辑,单例管理
    }
}

2. 装饰器模式 + 工厂模式

工厂模式负责创建复杂的装饰器链,隐藏构造细节。

public class IODecoratorFactory {
    public static InputStream createBufferedCompressedStream(File file) {
        return new BufferedInputStream(
                 new GZIPInputStream(
                   new FileInputStream(file)));
    }
}

3. 外观模式 + 抽象工厂模式

抽象工厂创建一系列相关的子系统对象,外观模式封装其调用。

class ReportFacade {
    private ReportBuilder builder;
    private ReportExporter exporter;
    public ReportFacade(ReportToolFactory factory) {
        builder = factory.createBuilder();
        exporter = factory.createExporter();
    }
    public void generateReport() { builder.build(); exporter.export(); }
}

4. 享元模式 + 建造者模式

对于复杂的享元对象(如字体样式包含多个属性),使用建造者模式构建不可变享元。

FontFlyweight font = new FontFlyweight.Builder()
    .name("Arial").size(12).bold(true).build();

5. 组合模式 + 访问者模式

树形结构接受访问者,执行特定操作,避免污染组件接口。

interface Component { void accept(Visitor v); }
class Composite implements Component {
    public void accept(Visitor v) {
        v.visitComposite(this);
        for (Component c : children) c.accept(v);
    }
}

6. 适配器模式 + 策略模式

适配器适配不同算法接口,策略模式统一调用。

interface SortStrategy { void sort(List list); }
class LegacySortAdapter implements SortStrategy {
    private LegacySorter sorter = new LegacySorter();
    public void sort(List list) { sorter.legacySort(list.toArray()); }
}