设计模式系列(13):结构型模式 - 外观模式

系列导读:在学习了功能装饰后,我们来看如何简化复杂系统的使用。外观模式为复杂的子系统提供了一个简单统一的接口。本系列同步更新在微信公众号: 码农秋, 欢迎关注。

解决什么问题:为子系统中的一组接口提供一个统一的高层接口,使子系统更容易使用。隐藏系统复杂性,提供简化的调用方式。

在实际项目中,我们经常需要调用多个子系统来完成一个业务操作。比如,用户下单可能需要调用库存系统、支付系统、物流系统、短信系统等。如果让客户端直接调用这些系统,代码会变得复杂且容易出错。

外观模式就像酒店的前台一样,客户只需要告诉前台自己的需求,前台会协调各个部门来满足客户需求。客户不需要知道酒店内部的复杂流程,只需要通过前台这个简单接口就能享受服务。

本文在系列中的位置

  • 前置知识:装饰器模式
  • 系列角色:结构型模式实用型
  • 难度等级:★★☆☆☆(概念简单,实用性强)
  • 后续学习:享元模式

目录

1. 模式概述

外观模式是一种结构型设计模式,它为复杂的子系统提供一个简单的接口。外观模式通过定义一个高层接口,使得子系统更容易使用,同时降低了客户端与子系统之间的耦合度。通过外观类,客户端可以只与高层接口交互,无需了解子系统的复杂实现细节。

1.1. 定义

为子系统中的一组接口提供一个统一的接口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观类封装了子系统的复杂性,对外暴露简洁的操作入口。

1.2. 目的

  • 简化复杂系统的使用,提升开发效率。
  • 降低系统间的耦合度,便于维护和扩展。
  • 提供统一的接口,隐藏子系统实现细节。

2. 使用场景

外观模式适用于以下场景:

  1. 系统集成

    • 需要集成多个子系统,简化调用流程。
    • 需要简化系统接口,提升易用性。
    • 需要降低系统耦合,便于后续维护。
  2. 分层设计

    • 需要分层架构,隔离不同层次的实现。
    • 需要隐藏实现细节,暴露统一接口。
    • 需要提供统一入口,便于上层调用。
  3. 系统重构

    • 需要重构复杂系统,简化对外接口。
    • 需要保持向后兼容,平滑过渡。
    • 需要简化系统使用,降低学习成本。

真实业务背景举例:

  • 大型企业的ERP系统,外观类统一封装财务、采购、库存等子系统,简化业务调用。
  • Web应用的服务层,外观类统一封装底层DAO、缓存、日志等子系统,提升开发效率。
  • 金融行业的支付网关,外观类统一封装多家银行、第三方支付接口,简化业务接入。
  • 电商平台的订单处理系统,外观类统一封装库存、物流、支付等子系统,提升业务开发效率。

3. 优缺点分析

3.1. 优点

  1. 简化使用:简化系统接口,降低使用难度。提高开发效率,便于团队协作。客户端无需了解子系统细节。
  2. 解耦:降低系统耦合,提升灵活性。便于系统维护和升级。支持子系统独立演化。
  3. 封装:隐藏实现细节,提升系统安全性。提供统一接口,便于扩展和维护。支持权限控制和安全校验。

3.2. 缺点

  1. 灵活性:可能限制灵活性,部分高级功能无法暴露。可能影响扩展性,需合理设计外观接口。可能增加系统复杂度,需权衡设计。
  2. 性能影响:可能影响性能,增加调用开销。可能影响执行效率,需优化实现。需避免外观类成为性能瓶颈。
  3. 维护成本:需要维护外观类,保证接口一致性。需要处理版本兼容和异常情况。需合理划分职责,避免外观类过于臃肿。

4. 实际应用案例

  1. 系统集成:多系统集成,统一入口简化调用。服务整合,提升业务开发效率。接口统一,便于外部系统对接。
  2. 框架设计:框架封装,隐藏底层实现细节。API设计,暴露简洁易用的接口。工具类封装,提升开发效率。
  3. 应用开发:模块封装,简化业务逻辑。功能整合,提升系统可维护性。接口简化,降低学习和使用成本。
  4. 系统重构:系统改造,平滑过渡新旧系统。接口升级,兼容历史调用方式。兼容性处理,降低重构风险。

5. 结构与UML类图

@startuml
package "Facade Pattern" #DDDDDD {
  class SystemFacade {
    + saveData(path: String, data: String): void
    + loadData(path: String): String
    + deleteData(path: String): void
    - fileSystem: FileSystem
    - cacheSystem: CacheSystem
    - logSystem: LogSystem
  }
  class FileSystem {
    + save(path: String, data: String): void
    + load(path: String): String
    + deleteFile(path: String): void
  }
  class CacheSystem {
    + put(key: String, value: String): void
    + get(key: String): String
    + remove(key: String): void
  }
  class LogSystem {
    + log(message: String): void
    + error(message: String): void
  }
  SystemFacade o-- FileSystem : fileSystem
  SystemFacade o-- CacheSystem : cacheSystem
  SystemFacade o-- LogSystem : logSystem
}
@enduml

6. 代码示例

6.1 基本结构示例

业务背景: 系统包含多个子系统,客户端希望通过统一入口简化操作,避免直接依赖各子系统。

package com.example.patterns.facade;

import java.util.Objects;

// 子系统A,提供独立功能
public class SubsystemA {
    public void operationA() {
        try {
            System.out.println("SubsystemA: 执行操作A");
        } catch (Exception e) {
            System.err.println("SubsystemA操作失败: " + e.getMessage());
            throw new RuntimeException("SubsystemA操作失败", e);
        }
    }
}

// 子系统B,提供独立功能
public class SubsystemB {
    public void operationB() {
        try {
            System.out.println("SubsystemB: 执行操作B");
        } catch (Exception e) {
            System.err.println("SubsystemB操作失败: " + e.getMessage());
            throw new RuntimeException("SubsystemB操作失败", e);
        }
    }
}

// 子系统C,提供独立功能
public class SubsystemC {
    public void operationC() {
        try {
            System.out.println("SubsystemC: 执行操作C");
        } catch (Exception e) {
            System.err.println("SubsystemC操作失败: " + e.getMessage());
            throw new RuntimeException("SubsystemC操作失败", e);
        }
    }
}

// 外观类,统一封装子系统操作
public class Facade {
    private final SubsystemA subsystemA;
    private final SubsystemB subsystemB;
    private final SubsystemC subsystemC;
    
    /**
     * 构造方法初始化各子系统,便于统一管理
     */
    public Facade() {
        this.subsystemA = new SubsystemA();
        this.subsystemB = new SubsystemB();
        this.subsystemC = new SubsystemC();
    }
    
    /**
     * 统一操作1,封装多个子系统调用
     */
    public void operation1() {
        System.out.println("Facade: 开始执行复合操作1");
        subsystemA.operationA();
        subsystemB.operationB();
        System.out.println("Facade: 复合操作1完成");
    }
    
    /**
     * 统一操作2,封装多个子系统调用
     */
    public void operation2() {
        System.out.println("Facade: 开始执行复合操作2");
        subsystemB.operationB();
        subsystemC.operationC();
        System.out.println("Facade: 复合操作2完成");
    }
}

6.2 企业级应用场景:电商订单处理系统

业务背景: 电商订单处理涉及库存、支付、物流等多个子系统,通过外观模式统一封装,简化业务流程。

package com.example.patterns.facade.ecommerce;

import java.math.BigDecimal;
import java.time.LocalDateTime;

// 库存管理子系统
public class InventorySystem {
    public boolean checkStock(String productId, int quantity) {
        System.out.println("库存系统: 检查商品 " + productId + " 库存数量 " + quantity);
        // 模拟库存检查逻辑
        return quantity <= 100; // 假设库存充足
    }
    
    public void reduceStock(String productId, int quantity) {
        System.out.println("库存系统: 扣减商品 " + productId + " 库存 " + quantity + " 件");
    }
    
    public void restoreStock(String productId, int quantity) {
        System.out.println("库存系统: 恢复商品 " + productId + " 库存 " + quantity + " 件");
    }
}

// 支付子系统
public class PaymentSystem {
    public boolean processPayment(String orderId, BigDecimal amount, String paymentMethod) {
        System.out.println("支付系统: 处理订单 " + orderId + " 支付,金额: ¥" + amount + ",支付方式: " + paymentMethod);
        // 模拟支付处理逻辑
        return amount.compareTo(BigDecimal.valueOf(10000)) <= 0; // 假设金额小于等于10000时支付成功
    }
    
    public void refund(String orderId, BigDecimal amount) {
        System.out.println("支付系统: 订单 " + orderId + " 退款,金额: ¥" + amount);
    }
}

// 物流子系统
public class LogisticsSystem {
    public String createShipment(String orderId, String address) {
        String trackingNumber = "TN" + System.currentTimeMillis();
        System.out.println("物流系统: 创建订单 " + orderId + " 物流单,跟踪号: " + trackingNumber + ",配送地址: " + address);
        return trackingNumber;
    }
    
    public void cancelShipment(String trackingNumber) {
        System.out.println("物流系统: 取消物流单 " + trackingNumber);
    }
}

// 订单管理子系统
public class OrderSystem {
    public String createOrder(String customerId, String productId, int quantity, BigDecimal price) {
        String orderId = "ORD" + System.currentTimeMillis();
        System.out.println("订单系统: 创建订单 " + orderId + ",客户: " + customerId + ",商品: " + productId);
        return orderId;
    }
    
    public void updateOrderStatus(String orderId, String status) {
        System.out.println("订单系统: 更新订单 " + orderId + " 状态为: " + status);
    }
    
    public void cancelOrder(String orderId) {
        System.out.println("订单系统: 取消订单 " + orderId);
    }
}

// 通知子系统
public class NotificationSystem {
    public void sendOrderConfirmation(String customerId, String orderId) {
        System.out.println("通知系统: 向客户 " + customerId + " 发送订单 " + orderId + " 确认通知");
    }
    
    public void sendShippingNotification(String customerId, String orderId, String trackingNumber) {
        System.out.println("通知系统: 向客户 " + customerId + " 发送订单 " + orderId + " 发货通知,跟踪号: " + trackingNumber);
    }
    
    public void sendCancellationNotification(String customerId, String orderId) {
        System.out.println("通知系统: 向客户 " + customerId + " 发送订单 " + orderId + " 取消通知");
    }
}

// 订单处理外观类
public class OrderProcessingFacade {
    private final InventorySystem inventorySystem;
    private final PaymentSystem paymentSystem;
    private final LogisticsSystem logisticsSystem;
    private final OrderSystem orderSystem;
    private final NotificationSystem notificationSystem;
    
    public OrderProcessingFacade() {
        this.inventorySystem = new InventorySystem();
        this.paymentSystem = new PaymentSystem();
        this.logisticsSystem = new logisticsSystem();
        this.orderSystem = new OrderSystem();
        this.notificationSystem = new NotificationSystem();
    }
    
    /**
     * 处理订单 - 统一的业务入口
     */
    public String processOrder(String customerId, String productId, int quantity, BigDecimal price, String paymentMethod, String address) {
        System.out.println("订单处理外观: 开始处理订单");
        
        try {
            // 1. 检查库存
            if (!inventorySystem.checkStock(productId, quantity)) {
                throw new RuntimeException("库存不足");
            }
            
            // 2. 创建订单
            String orderId = orderSystem.createOrder(customerId, productId, quantity, price);
            
            // 3. 扣减库存
            inventorySystem.reduceStock(productId, quantity);
            
            // 4. 处理支付
            BigDecimal totalAmount = price.multiply(BigDecimal.valueOf(quantity));
            if (!paymentSystem.processPayment(orderId, totalAmount, paymentMethod)) {
                // 支付失败,恢复库存
                inventorySystem.restoreStock(productId, quantity);
                orderSystem.cancelOrder(orderId);
                throw new RuntimeException("支付失败");
            }
            
            // 5. 创建物流
            String trackingNumber = logisticsSystem.createShipment(orderId, address);
            
            // 6. 更新订单状态
            orderSystem.updateOrderStatus(orderId, "已确认");
            
            // 7. 发送通知
            notificationSystem.sendOrderConfirmation(customerId, orderId);
            notificationSystem.sendShippingNotification(customerId, orderId, trackingNumber);
            
            System.out.println("订单处理外观: 订单 " + orderId + " 处理完成");
            return orderId;
            
        } catch (Exception e) {
            System.err.println("订单处理外观: 订单处理失败 - " + e.getMessage());
            throw new RuntimeException("订单处理失败", e);
        }
    }
    
    /**
     * 取消订单 - 统一的业务入口
     */
    public void cancelOrder(String customerId, String orderId, String productId, int quantity) {
        System.out.println("订单处理外观: 开始取消订单 " + orderId);
        
        try {
            // 1. 取消物流
            // logisticsSystem.cancelShipment(trackingNumber); // 需要根据订单查询跟踪号
            
            // 2. 处理退款
            // paymentSystem.refund(orderId, totalAmount); // 需要根据订单查询金额
            
            // 3. 恢复库存
            inventorySystem.restoreStock(productId, quantity);
            
            // 4. 更新订单状态
            orderSystem.cancelOrder(orderId);
            
            // 5. 发送通知
            notificationSystem.sendCancellationNotification(customerId, orderId);
            
            System.out.println("订单处理外观: 订单 " + orderId + " 取消完成");
            
        } catch (Exception e) {
            System.err.println("订单处理外观: 订单取消失败 - " + e.getMessage());
            throw new RuntimeException("订单取消失败", e);
        }
    }
}

7. 测试用例

业务背景: 验证外观模式的核心功能,包括基本外观功能和订单处理系统。

package com.example.patterns.facade.test;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import static org.junit.jupiter.api.Assertions.*;
import java.math.BigDecimal;

public class FacadePatternTest {
    
    private Facade basicFacade;
    private OrderProcessingFacade orderFacade;
    
    @BeforeEach
    public void setUp() {
        basicFacade = new Facade();
        orderFacade = new OrderProcessingFacade();
    }
    
    @Test
    public void testBasicFacadeOperations() {
        // 测试基本外观操作
        assertDoesNotThrow(() -> {
            basicFacade.operation1();
            basicFacade.operation2();
        });
    }
    
    @Test
    public void testOrderProcessingSuccess() {
        // 测试成功的订单处理
        String orderId = orderFacade.processOrder(
                "CUST001", 
                "PROD001", 
                2, 
                BigDecimal.valueOf(99.99), 
                "支付宝", 
                "北京市朝阳区xxx"
        );
        
        assertNotNull("订单ID不应为空", orderId);
        assertTrue("订单ID应以ORD开头", orderId.startsWith("ORD"));
    }
    
    @Test
    public void testOrderProcessingFailure() {
        // 测试订单处理失败(金额过大导致支付失败)
        assertThrows(RuntimeException.class, () -> {
            orderFacade.processOrder(
                    "CUST002", 
                    "PROD002", 
                    1, 
                    BigDecimal.valueOf(20000), // 超过支付限额
                    "微信支付", 
                    "上海市浦东新区xxx"
            );
        });
    }
    
    @Test
    public void testOrderCancellation() {
        // 测试订单取消
        assertDoesNotThrow(() -> {
            orderFacade.cancelOrder("CUST001", "ORD123456", "PROD001", 2);
        });
    }
}

8. 常见误区与反例

8.1 常见误区

  • 误区1 :外观类承担过多职责,成为"上帝类"
  • 误区2 :外观类与子系统强耦合,难以扩展
  • 误区3 :忽略异常处理,没有统一的错误处理机制

8.2 反例分析

  • 反例1 :直接在外观类中实现业务逻辑,违反单一职责原则
  • 反例2 :外观类暴露子系统的实现细节
  • 反例3 :没有考虑并发安全问题

9. 最佳实践

9.1 设计原则

  1. 保持外观类的简洁性 :只做协调和封装,不包含复杂业务逻辑
  2. 合理划分子系统 :确保子系统职责单一且松散耦合
  3. 提供多层次的外观 :根据不同的使用场景提供不同级别的外观

9.2 实现技巧

  1. 使用依赖注入 :提高外观类的可测试性和可配置性
  2. 实现统一的异常处理 :在外观层统一处理和转换异常
  3. 支持配置化 :允许通过配置灵活调整子系统的调用策略

10. 参考资料与延伸阅读

  • 《设计模式:可复用面向对象软件的基础》GoF
  • Effective Java(中文版)
  • https://refactoringguru.cn/design-patterns/facade
  • https://www.baeldung.com/java-facade-pattern

本文为设计模式系列第13篇,后续每篇将聚焦一个设计模式或设计原则,深入讲解实现与应用,敬请关注。微信公众号: 码农秋,欢迎关注。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值