结构型模式 “全家桶”:适配、装饰、代理…7 种模式让你的代码更 “有章法”

深入解析常见结构型设计模式:原理、应用与对比

在软件开发中,随着系统规模扩大,类与对象的关系会变得复杂,如何合理组织它们以提升复用性、灵活性和可维护性,成为开发者面临的核心问题。结构型设计模式正是为解决这一问题而生,它聚焦于类与对象的组合方式,通过优化结构降低耦合度,让系统更易扩展和维护。

根据组合的核心依赖不同,结构型模式可分为类结构型模式对象结构型模式。类结构型模式依赖继承机制,将多个类的功能组合到一起,典型代表是适配器模式和桥接模式;对象结构型模式则依赖组合或聚合关系,通过对象间的动态关联实现功能扩展,更符合 "合成复用原则",如装饰器模式、代理模式等,也是实际开发中更常用的类型。

一、7 种常见结构型模式详解(如果想要快速了解可以直接看最后的表格)

1. 适配器模式:解决 "接口不兼容" 的桥梁

核心原理

适配器模式的本质是 "转换接口"------ 当现有类的接口与客户端期望的接口不一致时,通过一个 "适配器" 类,将现有类的接口转换成客户端能识别的格式,让原本因接口不兼容无法协作的类可以一起工作。它就像生活中 "电源转换器",让不同插头的设备能在同一插座上使用。

应用场景
  • 集成第三方组件时,第三方接口与项目内部接口不匹配(如调用支付接口,第三方返回格式与项目统一返回格式不同);
  • 维护旧系统时,旧模块接口无法修改,但新模块需要调用旧模块功能;
  • 复用现有类,但该类的接口不符合当前业务需求。
实例说明

假设项目中需要计算 "矩形面积",现有一个LegacyRectangle类(旧系统遗留),它的方法是getLength()和getWidth(),但当前业务代码期望调用getX()(对应长度)和getY()(对应宽度)的接口。此时可创建一个适配器类RectangleAdapter,实现业务期望的接口,并在内部持有LegacyRectangle对象,通过适配器方法将旧接口转换为新接口:

csharp 复制代码
// 旧系统类(接口不兼容)
class LegacyRectangle {
    private int length;
    private int width;
    public int getLength() { return length; }
    public int getWidth() { return width; }
}
// 业务期望的接口
interface TargetShape {
    int getX();
    int getY();
}
// 适配器类
class RectangleAdapter implements TargetShape {
    private LegacyRectangle legacyRect;
    public RectangleAdapter(LegacyRectangle legacyRect) {
        this.legacyRect = legacyRect;
    }
    @Override
    public int getX() {
        return legacyRect.getLength(); // 转换旧接口为新接口
    }
    @Override
    public int getY() {
        return legacyRect.getWidth();
    }
}

2. 装饰器模式:动态给对象 "加功能"

核心原理

装饰器模式的核心是 "动态扩展"------ 在不修改原有对象代码、不改变对象结构的前提下,通过创建 "装饰器" 对象,将新功能附加到原有对象上,且可以灵活组合多个装饰器,实现功能的叠加。它就像给手机 "贴钢化膜、装手机壳",手机本身功能不变,但新增了防摔、防刮的功能,且可以随时更换装饰。

应用场景
  • 需要给对象动态添加功能,且功能可随时撤销(如给文本添加 "加粗""斜体""下划线",可单独添加也可组合,还能取消某一效果);
  • 避免通过继承生成大量子类(如一个文本类,若通过继承实现 "加粗文本""斜体文本""加粗 + 斜体文本",会产生多个子类,装饰器模式可通过组合解决);
  • 扩展的功能需要灵活组合(如日志系统,可动态添加 "时间戳日志""用户 ID 日志""错误级别日志")。
实例说明

以 "文本格式化" 为例,基础类Text实现 "显示文本" 功能,通过装饰器BoldDecorator(加粗)、ItalicDecorator(斜体)动态扩展功能:

scala 复制代码
// 基础文本类(被装饰者)
class Text {
    public String display() {
        return "原始文本";
    }
}
// 装饰器抽象类(持有被装饰者对象)
abstract class TextDecorator extends Text {
    protected Text text;
    public TextDecorator(Text text) {
        this.text = text;
    }
}
// 加粗装饰器
class BoldDecorator extends TextDecorator {
    public BoldDecorator(Text text) {
        super(text);
    }
    @Override
    public String display() {
        return "<b>" + text.display() + "</b>"; // 附加加粗功能
    }
}
// 斜体装饰器
class ItalicDecorator extends TextDecorator {
    public ItalicDecorator(Text text) {
        super(text);
    }
    @Override
    public String display() {
        return "<i>" + text.display() + "</i>"; // 附加斜体功能
    }
}
// 使用时可灵活组合:加粗+斜体
Text text = new ItalicDecorator(new BoldDecorator(new Text()));
System.out.println(text.display()); // 输出:<i><b>原始文本</b></i>

3. 代理模式:给对象 "找个代言人"

核心原理

代理模式的本质是 "控制访问"------ 为某个对象(目标对象)创建一个 "代理对象",客户端不直接访问目标对象,而是通过代理对象间接访问。代理对象可以在访问目标对象前后,添加额外操作(如权限校验、日志记录、缓存处理),从而实现对目标对象的保护或功能增强。它就像 "房产中介",购房者不直接与房东沟通,而是通过中介完成看房、议价、签约等流程,中介还能提供房源筛选、合同审核等额外服务。

应用场景
  • 需要对目标对象进行权限控制(如只有管理员能调用某个接口,代理对象先校验权限);
  • 调用目标对象前需要预处理(如参数校验、日志记录)或调用后需要后处理(如结果缓存、异常处理);
  • 目标对象创建成本高(如数据库连接对象),通过代理实现 "延迟加载"(仅在需要时创建目标对象)。
实例说明

以 "用户查询接口" 为例,目标对象UserService实现查询用户信息的功能,代理对象UserServiceProxy在查询前校验用户是否登录:

typescript 复制代码
// 目标对象接口
interface UserService {
    User queryUser(String userId);
}
// 目标对象实现
class UserServiceImpl implements UserService {
    @Override
    public User queryUser(String userId) {
        // 实际查询数据库的逻辑
        return new User(userId, "张三");
    }
}
// 代理对象
class UserServiceProxy implements UserService {
    private UserService userService;
    private LoginChecker loginChecker; // 权限校验工具
    public UserServiceProxy(UserService userService) {
        this.userService = userService;
        this.loginChecker = new LoginChecker();
    }
    @Override
    public User queryUser(String userId) {
        // 调用目标对象前:校验登录
        if (!loginChecker.isLoggedIn()) {
            throw new RuntimeException("请先登录");
        }
        // 调用目标对象
        User user = userService.queryUser(userId);
        // 调用后:记录查询日志
        System.out.println("用户" + userId + "查询成功");
        return user;
    }
}

4. 门面模式(外观模式):给复杂系统 "搭个简化入口"

核心原理

门面模式的核心是 "简化接口"------ 当一个子系统包含多个复杂的组件和接口时,创建一个 "门面类",将子系统的多个接口封装成一个统一的高层接口,客户端只需调用门面类的方法,无需关心子系统内部的复杂逻辑。它就像 "餐厅服务员",顾客不需要直接与后厨、收银台、配菜间沟通,只需告诉服务员需求,由服务员协调内部流程。

应用场景
  • 子系统结构复杂,客户端需要调用多个接口才能完成一个功能(如 "用户下单" 需要调用 "库存检查""订单创建""支付处理""物流生成" 4 个接口,门面类可封装为createOrder()一个接口);
  • 希望隔离客户端与子系统,降低客户端对於系统的依赖(如更换子系统时,只需修改门面类,客户端代码无需改动);
  • 维护旧系统时,为旧系统提供统一入口,减少新系统与旧系统的耦合。
实例说明

以 "电商下单" 为例,子系统包含InventoryService(库存检查)、OrderService(订单创建)、PaymentService(支付处理),门面类OrderFacade封装下单流程:

arduino 复制代码
// 子系统1:库存服务
class InventoryService {
    public boolean checkStock(String productId, int count) {
        // 检查库存是否充足
        return true;
    }
}
// 子系统2:订单服务
class OrderService {
    public String createOrder(String userId, String productId, int count) {
        // 创建订单,返回订单号
        return "ORDER123456";
    }
}
// 子系统3:支付服务
class PaymentService {
    public boolean pay(String orderId, double amount) {
        // 处理支付
        return true;
    }
}
// 门面类
class OrderFacade {
    private InventoryService inventoryService;
    private OrderService orderService;
    private PaymentService paymentService;
    public OrderFacade() {
        this.inventoryService = new InventoryService();
        this.orderService = new OrderService();
        this.paymentService = new PaymentService();
    }
    // 封装下单流程,提供统一接口
    public String createOrder(String userId, String productId, int count, double amount) {
        // 1. 检查库存
        if (!inventoryService.checkStock(productId, count)) {
            throw new RuntimeException("库存不足");
        }
        // 2. 创建订单
        String orderId = orderService.createOrder(userId, productId, count);
        // 3. 处理支付
        if (!paymentService.pay(orderId, amount)) {
            throw new RuntimeException("支付失败");
        }
        return orderId;
    }
}
// 客户端使用:只需调用门面类的一个方法
OrderFacade facade = new OrderFacade();
String orderId = facade.createOrder("USER001", "PROD001", 2, 299.8);

5. 桥接模式:解决 "多维度变化" 的耦合

核心原理

桥接模式的本质是 "分离抽象与实现"------ 当一个类存在两个或多个独立变化的维度(如 "形状" 和 "颜色",形状可分为圆形、矩形,颜色可分为红色、蓝色),若通过继承组合(如RedCircle、BlueCircle、RedRectangle、BlueRectangle),会产生大量子类(子类数量 = 维度 1 数量 × 维度 2 数量)。桥接模式通过 "组合" 代替 "继承",将两个维度分离为独立的抽象类和实现类,让它们可以各自独立扩展,再通过 "桥接" 关联起来。

应用场景
  • 类存在多个独立变化的维度,且每个维度都可能扩展(如 "操作系统" 和 "软件",操作系统可扩展为 Windows、Linux,软件可扩展为浏览器、办公软件);
  • 避免通过继承产生大量子类,导致系统臃肿;
  • 希望抽象层和实现层可以独立变化(如修改软件的实现逻辑,不影响操作系统的抽象,反之亦然)。
实例说明

以 "图形绘制" 为例,存在 "图形类型"(圆形、矩形)和 "颜色"(红色、蓝色)两个维度,用桥接模式分离:

java 复制代码
// 维度1:颜色(实现层,可独立扩展)
interface Color {
    void fill();
}
class Red implements Color {
    @Override
    public void fill() {
        System.out.print("红色");
    }
}
class Blue implements Color {
    @Override
    public void fill() {
        System.out.print("蓝色");
    }
}
// 维度2:图形(抽象层,持有颜色对象,通过桥接关联)
abstract class Shape {
    protected Color color; // 桥接:抽象层持有实现层对象
    public Shape(Color color) {
        this.color = color;
    }
    abstract void draw();
}
class Circle extends Shape {
    public Circle(Color color) {
        super(color);
    }
    @Override
    void draw() {
        color.fill();
        System.out.println("圆形");
    }
}
class Rectangle extends Shape {
    public Rectangle(Color color) {
        super(color);
    }
    @Override
    void draw() {
        color.fill();
        System.out.println("矩形");
    }
}
// 使用时:灵活组合两个维度
Shape redCircle = new Circle(new Red());
redCircle.draw(); // 输出:红色圆形
Shape blueRectangle = new Rectangle(new Blue());
blueRectangle.draw(); // 输出:蓝色矩形

6. 组合模式:处理 "部分 - 整体" 的树形结构

核心原理

组合模式的本质是 "统一部分与整体"------ 当对象呈现 "部分 - 整体" 的层次结构(如文件夹与文件,文件夹包含子文件夹和文件,子文件夹又可包含更小的文件夹和文件),通过组合模式让 "部分"(如文件)和 "整体"(如文件夹)实现相同的接口,客户端可以用统一的方式处理单个对象和组合对象,无需区分是 "部分" 还是 "整体"。

应用场景
  • 数据结构呈树形结构(如组织架构:公司→部门→小组→员工;文件系统:根目录→子目录→文件);
  • 客户端需要统一处理单个对象和组合对象(如计算 "文件夹大小",客户端只需调用getSize(),无需关心是文件夹还是文件,文件夹会自动递归计算子文件大小);
  • 希望动态添加或删除树形结构中的节点(如给文件夹添加子文件夹或文件)。
实例说明

以 "文件系统" 为例,FileComponent是统一接口,File(部分)和Folder(整体)实现该接口,Folder可包含多个FileComponent对象:

csharp 复制代码
// 统一接口(部分和整体都实现)
interface FileComponent {
    long getSize(); // 计算大小
    String getName();
}
// 部分:文件
class File implements FileComponent {
    private String name;
    private long size;
    @Override
    public long getSize() {
        return size; // 文件大小固定
    }
    @Override
    public String getName() {
        return name;
    }
}
// 整体:文件夹
class Folder implements FileComponent {
    private String name;
    private List<FileComponent> children = new ArrayList<>();
    // 添加子组件(文件或文件夹)
    public void add(FileComponent component) {
        children.add(component);
    }
    // 删除子组件
    public void remove(FileComponent component) {
        children.remove(component);
    }
    @Override
    public long getSize() {
        // 递归计算所有子组件的大小
        return children.stream().mapToLong(FileComponent::getSize).sum();
    }
    @Override
    public String getName() {
        return name;
    }
}
// 客户端使用:统一处理文件夹和文件
FileComponent docFile = new File("文档.txt", 1024);
FileComponent imgFile = new File("图片.png", 2048);
Folder docFolder = new Folder("文档文件夹");
docFolder.add(docFile);
docFolder.add(imgFile);
System.out.println(docFile.getSize()); // 输出:1024
System.out.println(docFolder.getSize()); // 输出:3072(1024+2048)

7. 享元模式:减少对象创建的 "内存优化师"

核心原理

享元模式的本质是 "复用对象"------ 当系统中存在大量相同或相似的对象(如围棋中的棋子,只有 "黑""白" 两种类型,却有上百个棋子对象),通过共享这些对象的 "公共部分"(如棋子颜色),只创建少量不同的对象,减少内存占用和对象创建开销。它将对象的属性分为 "内部状态"(可共享,如棋子颜色)和 "外部状态"(不可共享,如棋子位置),内部状态相同的对象可共享,外部状态通过参数传递。

应用场景
  • 系统中存在大量相同或相似的对象,且对象创建成本高(如数据库连接池,复用连接对象,避免频繁创建和关闭连接);
  • 对象的大部分属性是可共享的内部状态,少量属性是需要动态设置的外部状态;
  • 内存资源紧张,需要减少对象数量以降低内存消耗(如游戏中的粒子效果,大量粒子只需共享 "形状""颜色"

各模式的核心权衡点

设计模式 核心优点 主要缺点 适用场景总结
适配器模式 1. 无需修改原有代码,解决接口不兼容问题,符合 "开闭原则";2. 复用现有类,降低开发成本;3. 灵活切换适配对象,提升系统扩展性。 1. 过多使用会增加系统复杂度,导致调用链路变长;2. 适配复杂接口时,代码逻辑可能繁琐。 集成第三方组件、维护旧系统、接口格式不匹配的场景。
装饰器模式 1. 动态扩展功能,可随时添加 / 移除装饰,灵活性高;2. 避免通过继承生成大量子类,减少系统冗余;3. 装饰器可组合使用,实现功能叠加。 1. 多层装饰会增加对象创建成本,影响性能;2. 调试时需跟踪多层装饰链路,排查问题较复杂。 需动态扩展功能、功能可组合、避免子类膨胀的场景(如文本格式化、日志增强)。
代理模式 1. 隔离客户端与目标对象,保护目标对象;2. 可在访问前后添加额外操作(如权限校验、日志);3. 支持延迟加载,降低初始化成本。 1. 增加代理对象,导致系统层级变多;2. 简单场景下会增加不必要的代码复杂度。 权限控制、日志记录、延迟加载、远程调用(如 RPC 代理)的场景。
门面模式 1. 简化客户端调用,隐藏子系统复杂度;2. 降低客户端与子系统的耦合,便于子系统维护;3. 统一入口,便于监控和管理。 1. 门面类可能成为 "上帝类",职责过重;2. 子系统扩展时,可能需要修改门面类,违反 "开闭原则"。 子系统复杂、客户端需统一调用入口、需隔离子系统的场景(如电商下单流程)。
桥接模式 1. 分离抽象与实现,两者可独立扩展,减少子类数量;2. 灵活切换抽象或实现,适应多维度变化;3. 提高代码复用性,降低耦合。 1. 增加系统设计复杂度,需理解抽象与实现的分离逻辑;2. 简单场景下会过度设计。 类存在多维度变化(如形状 + 颜色、操作系统 + 软件)、需避免子类膨胀的场景。
组合模式 1. 统一处理部分与整体,客户端无需区分单个对象与组合对象;2. 支持动态添加 / 删除树形节点,扩展性强;3. 便于遍历和操作树形结构。 1. 限制节点类型时,需在代码中添加判断,违反 "开闭原则";2. 树形结构过深时,遍历性能可能下降。 数据呈树形结构(如文件系统、组织架构)、需统一处理部分与整体的场景。
享元模式 1. 复用对象,减少内存占用,降低对象创建成本;2. 集中管理共享对象,便于维护;3. 提升系统性能,尤其适合大量相似对象场景。 1. 分离内部 / 外部状态,增加代码复杂度;2. 外部状态需通过参数传递,可能影响使用便捷性。 存在大量相似对象、内存资源紧张、对象可拆分内部 / 外部状态的场景(如围棋棋子、连接池)。
相关推荐
萧瑟余晖4 小时前
JDK 26 新特性详解
java·开发语言
马优晨5 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
维天说7 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json
Zane19948 小时前
并发 vs 并行:别再傻傻分不清了,一文讲透 Java 并发编程的第一课
java·后端
杨充9 小时前
11.DDD与战术建模
设计模式·开源·代码规范
杨充9 小时前
12.综合实战图片框架
设计模式·开源·代码规范
噢,我明白了9 小时前
java中Excel的导入和导出(EasyExcel)
java·开发语言·excel
吠品9 小时前
Zabbix Web界面误报Server未运行的排查与解决
java·服务器·数据库
啊湘9 小时前
天气查询API接口 按月Token鉴权 实时天气 物联网可用 文档齐全
java·后端·struts
Java内核笔记10 小时前
告别十亿美元的错误 : Spring Boot 4 空安全 (JSpecify) 实战
java·spring boot·后端