Skip to content

结构型模式总览

结构型模式关注类与对象如何组合。它们解决的不是业务规则本身,而是接口不兼容、功能叠加、访问控制、子系统简化、树形组织、对象共享等结构问题。

结构型模式总览

解决的核心问题

结构型模式常出现在这些场景:

  • 新系统想复用老接口或第三方库,但接口对不上。
  • 需要给对象增加日志、缓存、加密、压缩、权限等能力。
  • 访问真实对象前需要做控制,例如远程调用、懒加载、鉴权。
  • 子系统内部复杂,外部只需要一个简单入口。
  • 对象组成树形结构,例如菜单、文件、组织架构。
  • 系统有多个变化维度,继承层次开始膨胀。
  • 大量小对象占用内存,需要共享可复用部分。

结构型模式的共同点是优先通过组合和封装调整关系,而不是直接修改已有类。

七种结构型模式

模式适合场景直观理解
适配器接口不兼容插头转换器
桥接抽象和实现都可能变化拆开两个变化维度
组合整体和部分需要一致处理文件夹和文件统一遍历
装饰器动态叠加功能一层层包装对象
外观简化复杂子系统统一服务窗口
享元大量对象共享状态把重复部分集中复用
代理控制对真实对象的访问访问目标前先经过代理

怎么选

接口不一致

选择适配器。重点是让旧接口、第三方接口或遗留系统能接入当前系统,而不直接修改原有代码。

功能要动态叠加

选择装饰器。比如输入流增加缓冲、压缩、加密能力;业务处理增加日志、校验、监控能力。

访问目标前要加控制

选择代理。比如远程代理、虚拟代理、保护代理、缓存代理。代理通常和真实对象实现相同接口。

子系统太复杂

选择外观。外观不是屏蔽所有能力,而是给常用流程提供稳定入口,降低调用方理解成本。

整体和部分同等对待

选择组合。树形菜单、目录结构、组织架构、表达式树都很适合。

两个维度同时扩展

选择桥接。比如形状和颜色、消息类型和发送渠道、设备抽象和具体驱动。桥接能避免继承组合爆炸。

大量对象重复

选择享元。重点是区分内部状态和外部状态,把可共享的内部状态缓存起来。

实操:结构问题怎么落地处理

场景一:第三方接口到处散落

问题代码:

java
public class OrderService {
    private final VendorSmsClient smsClient;

    public void createOrder(Order order) {
        // 创建订单
        Map<String, Object> params = new HashMap<>();
        params.put("orderNo", order.getOrderNo());
        smsClient.pushMessage(order.getPhone(), "ORDER_CREATED", params);
    }
}

业务类直接依赖厂商 SDK,后续换厂商或改模板时影响面大。可以通过适配器统一内部接口:

java
public interface NotificationSender {
    void orderCreated(Order order);
}

public class SmsNotificationAdapter implements NotificationSender {
    private final VendorSmsClient smsClient;

    public void orderCreated(Order order) {
        Map<String, Object> params = Map.of("orderNo", order.getOrderNo());
        smsClient.pushMessage(order.getPhone(), "ORDER_CREATED", params);
    }
}

OrderService 只依赖 NotificationSender,外部 SDK 变化被限制在适配器内部。

场景二:业务方法里重复出现缓存和鉴权

问题代码:

java
public UserProfile getProfile(Long userId) {
    checkPermission(userId);
    UserProfile cached = cache.get(userId);
    if (cached != null) {
        return cached;
    }
    UserProfile profile = remoteUserClient.getProfile(userId);
    cache.put(userId, profile);
    return profile;
}

如果很多方法都这样写,可以把访问控制和缓存放到代理层:

java
public interface UserProfileClient {
    UserProfile getProfile(Long userId);
}

public class UserProfileProxy implements UserProfileClient {
    private final UserProfileClient target;
    private final Cache<Long, UserProfile> cache;

    public UserProfile getProfile(Long userId) {
        checkPermission(userId);
        UserProfile cached = cache.get(userId);
        if (cached != null) {
            return cached;
        }
        UserProfile profile = target.getProfile(userId);
        cache.put(userId, profile);
        return profile;
    }
}

代理适合控制访问,不适合偷偷改变业务含义。调用方应该仍然认为自己在访问 UserProfileClient

场景三:复杂子系统需要统一入口

下单可能涉及库存、优惠、支付、消息、积分:

java
public class OrderFacade {
    public OrderResult submit(OrderCommand command) {
        inventoryService.lock(command);
        CouponResult coupon = couponService.calculate(command);
        PaymentResult payment = paymentService.pay(command, coupon);
        pointService.add(command.getUserId(), payment);
        messageService.sendOrderCreated(command);
        return OrderResult.success(payment.getOrderNo());
    }
}

外观模式适合把一组稳定流程收口成一个入口。它不应该吞掉所有细节,也不应该变成所有业务逻辑都塞进去的巨大类。

容易混淆的模式

模式容易混淆点区分方式
适配器 vs 外观都包了一层适配器解决接口不兼容,外观简化子系统
装饰器 vs 代理都包着真实对象装饰器增强能力,代理控制访问
桥接 vs 策略都依赖抽象桥接拆结构维度,策略替换算法行为
组合 vs 装饰器都可能递归组合组合表达树形整体,装饰器表达功能叠加

常见误区

  • 适配器过多会掩盖真实接口混乱,需要适时治理边界。
  • 装饰器层数太多会增加调试难度。
  • 代理不应悄悄改变真实对象语义。
  • 外观不能变成新的“上帝类”,只负责协调常用流程。
  • 享元要谨慎处理共享状态,避免把外部变化也共享出去。

结构型模式的关键是让对象关系更清楚。它们应该降低理解成本,而不是把简单结构包装得更复杂。

别急,先让缓存热一下。