模式组合应用-外观模式

写在前面

Hello,我是易元,这篇文章是我学习设计模式时的笔记和心得体会。如果其中有错误,欢迎大家留言指正!


外观模式

定义

结构型设计模式, 旨在为子系统中的一组接口提供一个统一的接口, 外观模式定义了一个高层接口, 这个接口使得子系统更容易使用。

通过引入一个 外观类 来解决多对多交互的复杂度, 这个外观类充当了客户端和复杂子系统之间的中间层。它封装了子系统内部的复杂性, 并向客户端提供一个简化的、统一的接口。

基本结构

外观模式的核心思想是 提供一个统一的、高层的接口, 来简化客户端对复杂子系统的访问。

  • 外观: 提供一个简化的接口, 客户端通过这个接口与子系统进行交互, 外观类知道哪些子系统类负责处理请求, 并将客户端的请求委派给适当的子系统对象。它不参与子系统内部的业务逻辑, 只负责协调子系统的工作。
  • 子系统: 是一组相互关联的类和接口, 它们共同完成了某个复杂功能。子系统类之间是相互独立的, 它们不了解外部类, 也不持有外观类的引用。只专注于自身的职责, 并提供公共的接口供外部调用。

使用时主要思考点

  • 外观模式适用于那些由多个紧密协作的类组成的复杂子系统, 需要明确哪些类应该被视为一个子系统, 并为其定义一个统一的外观接口。若子系统过于简单, 引入外观模式将增加不必要的复杂性。
  • 外观类的主要目的是提供一个简化的接口, 因此外观类的方法应该尽可能的少且高层, 只暴露客户端真正需要的功能, 隐藏子系统内部的复杂操作。
  • 外观类虽然协调子系统的工作, 但它不应该承担过多的业务逻辑。外观类应该专注于将客户端的请求委派给正确的子系统对象, 并协调它们之间的交互。
  • 子系统的类应保持独立, 不应该持有外观类的引用, 也不应该直接调用外观类的方法, 保持子系统的重用性和独立性。

外观模式+单例模式

单例模式(创建型)

保证一个类只有一个实例, 并提供一个访问该实例的全局节点

案例

假设一个电商平台的订单处理子系统, 包含订单创建、商品库存减扣、支付接口调用等多个复杂步骤, 希望客户端只需要通过一个统一的接口来完成订单的提交, 并且这个接口在整个应用生命周期中只有一个实例。

模式职责

  • 外观模式: 提供一个统一的接口, 封装订单处理子系统的复杂性, 简化客户端的订单提交操作。
  • 单例模式: 确保外观类只有一个实例, 并提供一个全局访问点, 避免重复创建和管理外观对象。

代码内容

外观类/单例类
arduino 复制代码
public class OrderProcessingFacade {

    private static OrderProcessingFacade instance;

    private InventoryService inventoryService;

    private PaymentService paymentService;

    private NotificationService notificationService;

    private OrderProcessingFacade() {
        inventoryService = new InventoryService();
        paymentService = new PaymentService();
        notificationService = new NotificationService();
    }

    public static synchronized OrderProcessingFacade getInstance() {
        if (instance == null) {
            instance = new OrderProcessingFacade();
        }

        return instance;
    }

    public boolean placeOrder(String productId, int quantity, String userEmail, double amount) {

        System.out.println("\n--- 订单处理: 开始处理订单 ---");

        String orderId = "ORDER_" + System.currentTimeMillis();

        inventoryService.deductStock(productId, quantity);

        boolean paymentSuccess = paymentService.makePayment(orderId, amount);

        if (paymentSuccess) {
            notificationService.sendOrderConfirmation(orderId, userEmail);
            System.out.println("\n--- 订单处理: 订单处理成功 ---");
            return true;
        }
        else {
            inventoryService.restoreStock(productId, quantity);
            notificationService.sendPaymentFailedNotification(orderId, userEmail);
            System.out.println("\n--- 订单处理: 订单处理失败 ---");
            return false;
        }

    }
}
  • 外观模式的核心类, 它聚合了 InventoryServicePaymentServiceNotificationService 的实例, 并提供了 placeOrder 方法,简化了客户端的下订单逻辑, 协调子系统类完成订单提交的复杂流程。
  • 单例模式的实现类, 通过私有构造函数和静态的 getInstance() 方法, 确保在整个应用程序中只有一个 OrderProcessingFacade 的实例。
外观模式部分
子系统类
typescript 复制代码
public class InventoryService {

    public void deductStock(String productId, int quantity) {
        System.out.println("库存服务: 成功减扣商品 " + productId + " 数量 " + quantity);
    }

    public void restoreStock(String productId, int quantity) {
        System.out.println("库存服务: 成功恢复商品 " + productId + " 数量 " + quantity);
    }

}
public class PaymentService {

    public boolean makePayment(String orderId, double amount) {
        System.out.println("支付服务: 订单 " + orderId + " 支付金额 " + amount + " 支付成功。");
        return true;
    }

    public void refund(String orderId, double amount) {
        System.out.println("支付服务: 订单 " + orderId + " 退款金额 " + amount + " 退款成功。");
    }

}
public class NotificationService {

    public void sendOrderConfirmation(String orderId, String userEmail) {
        System.out.println("通知服务: 已向 " + userEmail + " 发送订单 " + orderId + " 的确认通知。");
    }

    public void sendPaymentFailedNotification(String orderId, String userEmail) {
        System.out.println("通知服务: 已向 " + userEmail + " 发送订单 " + orderId + " 支付失败通知。");
    }

}
  • InventoryService 模拟库存管理功能, 包含 deductStock(库存减扣)和 restoreStock(恢复库存)。
  • PaymentService 模拟支付处理功能, 包含 makePayment(支付方法, 返回支付结果) 和 refund(退款方法)。
  • NotificationService 模拟通知发送功能, 包含 sendOrderConfirmation(发送订单确认方法) 和 sendPaymentFailedNotification(发送支付失败通知方法)。
测试类
csharp 复制代码
public class OrderTest {

    @Test
    public void test_order() {
        System.out.println("--- 客户端开始使用订单处理外观类 --");

        OrderProcessingFacade facade1 = OrderProcessingFacade.getInstance();
        OrderProcessingFacade facade2 = OrderProcessingFacade.getInstance();

        System.out.println("两个外观实例是否相同: " + (facade1 == facade2));

        facade1.placeOrder("商品A", 1, "user1@example.com", 999.99);

        System.out.println("\n--- 客户端再次使用订单处理外观 ---");
        facade2.placeOrder("商品B", 2, "user2@example.com", 49.9);

        System.out.println("--- 客户端结束使用订单处理外观类 ---");
    }

}
执行结果
sql 复制代码
--- 客户端开始使用订单处理外观类 --
两个外观实例是否相同: true

--- 订单处理: 开始处理订单 ---
库存服务: 成功减扣商品 商品A 数量 1
支付服务: 订单 ORDER_1757747198279 支付金额 999.99 支付成功。
通知服务: 已向 user1@example.com 发送订单 ORDER_1757747198279 的确认通知。

--- 订单处理: 订单处理成功 ---

--- 客户端再次使用订单处理外观 ---

--- 订单处理: 开始处理订单 ---
库存服务: 成功减扣商品 商品B 数量 2
支付服务: 订单 ORDER_1757747198280 支付金额 49.9 支付成功。
通知服务: 已向 user2@example.com 发送订单 ORDER_1757747198280 的确认通知。

--- 订单处理: 订单处理成功 ---
--- 客户端结束使用订单处理外观类 ---

Process finished with exit code 0

组合优势

  • 工厂方法模式将创建复杂对象的逻辑封装, 客户端无需了解角色如何被装饰创建, 只需要通过工厂方法获取所需角色, 降低客户端的耦合度。
  • 新增角色类型或新的默认装备组合时, 只需要增加新的具体工厂类和相应的装饰器类, 无需修改现有代码。

外观模式+策略模式

策略模式(行为型)

定义一系列算法, 将每个算法封装起来, 并使得它们可以相互替换。

案例

一个文档转换系统中, 用户可能需要将不同格式的文档(如: Word、Excel、PDF) 转换为统一的HTML格式, 每种文档格式的转换逻辑不同, 且未来可能会增加新的文档格式。同时客户端要求简化对于复杂转换过程的调用。

模式职责

  • 外观模式: 提供一个统一的接口, 不同文档格式转换的复杂性, 简化客户端的转换操作。
  • 策略模式: 封装不同的文档转换算法, 使它们可以相互替换, 并方便地扩展新的转换策略。

代码内容

策略模式部分
抽象策略接口
arduino 复制代码
public interface DocumentConversionStrategy {

    String convert(String filePath);

}
  • 定义了 convert 方法, 所有具体的文档转换算法都必须实现这个接口。这使得不同的转换算法可以互换, 并且客户端可以独立于具体的转换算法进行操作。
具体策略类/子系统类
typescript 复制代码
public class WordToHtmlStrategy implements DocumentConversionStrategy {

    @Override
    public String convert(String filePath) {
        System.out.println("正在将Word文档 " + filePath + " 转换为HTML...");

        return "<html><body><h1>Word Document: " + filePath + "</h1><p>Converted from Word.</p></body></html>";

    }
}
public class ExcelToHtmlStrategy implements DocumentConversionStrategy {

    @Override
    public String convert(String filePath) {

        System.out.println("正在将Excel文档 " + filePath + " 转换为HTML...");
        return "<html><body><h1>Excel Document: " + filePath + "</h1><table><tr><td>Converted from Excel.</td></tr></table></body></html>";

    }
}
public class PdfToHtmlStrategy implements DocumentConversionStrategy {

    @Override
    public String convert(String filePath) {
        System.out.println("正在将PDF文档 " + filePath + " 转换为HTML...");

        return "<html><body><h1>PDF Document: " + filePath + "</h1><p>Converted from PDF.</p></body></html>";
    }

}
  • 策略模式的具体策略角色, 实现了 DocumentConversionStrategy 接口, 分别模拟将 Word( WordToHtmlStrategy )、Excel( ExcelToHtmlStrategy )、PDF( PdfToHtmlStrategy ) 转换为 HTML。
上下文类/外观类
typescript 复制代码
public class DocumentConverterFacade {

    private DocumentConversionStrategy strategy;

    public void setConversionStrategy(String fileType) {
        switch (fileType.toLowerCase()) {
            case "docx":
                this.strategy = new WordToHtmlStrategy();
                break;
            case "xlsx":
                this.strategy = new ExcelToHtmlStrategy();
                break;
            case "pdf":
                this.strategy = new PdfToHtmlStrategy();
                break;
            default:
                throw new IllegalArgumentException("不支持的文档类型: " + fileType);
        }
    }

    public String convertDocumentToHtml(String filePath, String fileType) {
        System.out.println("\n--- 开始转换文档 ---");
        this.setConversionStrategy(fileType);
        if (this.strategy == null) {
            throw new IllegalStateException("未设置转换策略。");
        }

        String htmlContent = this.strategy.convert(filePath);
        System.out.println("\n--- 文档转换完成 ---");
        return htmlContent;
    }

}
  • 这个类同时做为了外观模式的外观角色和策略模式的上下文角色。
  • 作为外观, 它提供了一个简化的 convertDocumentToHtml 方法给客户端。
  • 作为上下文, 它持有一个 DocumentConversionStrategy 类型的引用, 并通过 setConversionStrategy 方法根据传入的类型动态地设置具体的转换策略。
测试类
csharp 复制代码
public class DocumentTest {

    @Test
    public void test_converter() {

        System.out.println("--- 客户端开始使用文档转换外观类 ---");

        DocumentConverterFacade converterFacade = new DocumentConverterFacade();

        String wordHtml = converterFacade.convertDocumentToHtml("report.docx", "docx");
        System.out.println("Word文档转换结果:\n" + wordHtml);

        String excelHtml = converterFacade.convertDocumentToHtml("data.xlsx", "xlsx");
        System.out.println("\nExcel文档转换结果:\n" + excelHtml);

        String pdfHtml = converterFacade.convertDocumentToHtml("manual.pdf", "pdf");
        System.out.println("\nPDF文档转换结果:\n" + pdfHtml);

        System.out.println("--- 客户端结束使用文档转换外观类 ---");

    }
}
执行结果
less 复制代码
--- 客户端开始使用文档转换外观类 ---

--- 开始转换文档 ---
正在将Word文档 report.docx 转换为HTML...

--- 文档转换完成 ---
Word文档转换结果:
<html><body><h1>Word Document: report.docx</h1><p>Converted from Word.</p></body></html>

--- 开始转换文档 ---
正在将Excel文档 data.xlsx 转换为HTML...

--- 文档转换完成 ---

Excel文档转换结果:
<html><body><h1>Excel Document: data.xlsx</h1><table><tr><td>Converted from Excel.</td></tr></table></body></html>

--- 开始转换文档 ---
正在将PDF文档 manual.pdf 转换为HTML...

--- 文档转换完成 ---

PDF文档转换结果:
<html><body><h1>PDF Document: manual.pdf</h1><p>Converted from PDF.</p></body></html>
--- 客户端结束使用文档转换外观类 ---

Process finished with exit code 0

组合优势

  • 外观模式隐藏了不同文档类型转换的复杂性, 客户端只需通过 DocumentConverterFacade 的统一接口进行操作, 无需关心内部的策略选择和具体转换细节。
  • 客户端与具体的转换算法之间通过外观类和策略接口解耦, 外观类与子系统之间也通过策略接口解耦, 使得系统更加灵活和可维护。

外观模式+观察者模式

观察者模式(行为型)

定义了对象之间一对多的依赖关系, 当一个对象的状态发生改变时, 所有依赖于它的对象都会得到通知并自动更新。

案例

以电商平台的客户服务系统为例, 完成以下功能: 当客户的会员等级发生变化时(如从普通会员升级为VIP会员), 需自动触发一系列操作: 发送升级通知邮件、更新客户在营销系统中的标签、通知客服团队关注该客户等。

模式职责

  • 外观模式: 封装客户信息更新的复杂性, 简化客户端的客户信息管理操作。
  • 观察者模式: 当客户信息发生重要变更时, 作为主题通知所有注册的观察者, 实现事件驱动的响应机制。

代码内容

辅助类
Customer
typescript 复制代码
public class Customer {

    private String id;

    private String name;

    private String email;

    private String level;

    public Customer(String id, String name, String email, String level) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.level = level;
    }

    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }

    public String getLevel() {
        return level;
    }

    public void setLevel(String level) {
        this.level = level;
    }

    @Override
    public String toString() {
        return "id ='" + id + "', name='" + name + "', email='" + email + "', level='" + level + "'";
    }

}
  • 表示客户实体, 包含ID、姓名、邮箱、会员等级等属性, 是观察者模式中主题和观察者之间传递的载体。
外观模式部分
子系统类
typescript 复制代码
public class CustomerRepository {

    public void save(Customer customer) {
        System.out.println("客户仓库: 保存客户信息 -> " + customer);
    }

    public Customer findById(String customerId) {
        System.out.println("客户仓库: 查找客户ID -> " + customerId);
        return new Customer(customerId, "张三", "test@example.com", "Normal");
    }

}
public class EmailService {

    public void sendEmail(String to, String subject, String body) {
        System.out.println("邮件服务: 发送邮件到 " + to + ", 主题: " + subject + ", 内容: " + body);
    }

}
public class MarketingService {

    public void updateCustomerTag(String customerId, String tag) {
        System.out.println("营销服务: 更新客户 " + customerId + " 的营销标签为 " + tag);
    }

}
  • CustomerRepository 模拟客户数据存储的子系统, 提供了 save(保存客户信息) 和 findById(根据ID查找客户)的方法。
  • EmailService 模拟邮件发送的子系统, 提供了 sendEmail方法, 用于发送邮件。
  • MarketingService 模拟营销系统同步的子系统, 提供了 updateCustomerTag 方法, 用于更新客户在营销系统中的标签。
观察者模式部分
抽象主题
csharp 复制代码
public interface CustomerSubject {

    void addObserver(CustomerObserver observer);

    void removeObserver(CustomerObserver observer);

    void notifyObserver(Customer customer);

}
  • 定义了主题必须实现在系列方法, 包含 addObserver(CustomerObserver observer)(注册观察者)、removeObserver(CustomerObserver observer)(移除观察者)、notifyObserver(Customer customer)(通知所有观察者)
抽象观察者
csharp 复制代码
public interface CustomerObserver {

    void update(Customer customer);

}
  • 定义了具体观察者必须实现的 update(Customer customer) 方法, 当主题状态变化时, 主题调用此方法来通知观察者。
具体观察者
typescript 复制代码
public class EmailNotificationObserver implements CustomerObserver {

    private EmailService emailService;

    public EmailNotificationObserver(EmailService emailService) {
        this.emailService = emailService;
    }

    @Override
    public void update(Customer customer) {

        if ("VIP".equals(customer.getLevel())) {
            String subject = "恭喜您升级为VIP会员!";
            String body = "亲爱的 " + customer.getName() + ", 恭喜您已成功升级为VIP会员, 享受更多专属权益!";
            emailService.sendEmail(customer.getEmail(), subject, body);
        }
    }
}
public class MarketingSyncObserver implements CustomerObserver {

    private MarketingService marketingService;

    public MarketingSyncObserver(MarketingService marketingService) {
        this.marketingService = marketingService;
    }

    @Override
    public void update(Customer customer) {
        marketingService.updateCustomerTag(customer.getId(), customer.getLevel());
    }
}
public class CustomerServiceAlertObserver implements CustomerObserver {

    @Override
    public void update(Customer customer) {
        System.out.println("客户提醒: 客户 " + customer.getName() + " (ID: " + customer.getId() + ") 等级更新为 " + customer.getLevel() + ", 请关注。");
    }
}
  • EmailNotificationObserver 包含一个 EmailService 引用, 通过构造函数进行初始化, 实现了 CustomerObserver 接口, 并在 update 方法中调用 EmailService 来发送邮件通知。
  • MarketingSyncObserver 包含一个 MarketingService 引用, 通过构造函数进行初始化, 实现了 CustomerObserver 接口, 并在 update 方法中调用 MarketingService 来同步客户的营销标签
  • CustomerServiceAlertObserver 实现了 CustomerObserver 接口, 并在 update 方法中模拟向客服团队发送提醒。
具体主题/外观类
typescript 复制代码
public class CustomerServiceFacade implements CustomerSubject {

    private CustomerRepository customerRepository;

    private List<CustomerObserver> observers = new ArrayList<>();

    public CustomerServiceFacade() {
        this.customerRepository = new CustomerRepository();
    }

    @Override
    public void addObserver(CustomerObserver observer) {
        observers.add(observer);
        System.out.println("观察者已添加: " + observer.getClass().getSimpleName());
    }

    @Override
    public void removeObserver(CustomerObserver observer) {
        observers.remove(observer);
        System.out.println("观察者已移除: " + observer.getClass().getSimpleName());
    }

    @Override
    public void notifyObserver(Customer customer) {
        System.out.println("通知所有观察者关于客户 " + customer.getId() + " 的更新...");
        for (CustomerObserver observer : observers) {
            observer.update(customer);
        }
    }

    public void updateCustomerLevel(String customerId, String newLevel) {
        System.out.println("\n--- 开始更新客户等级 ---");

        Customer customer = customerRepository.findById(customerId);

        if (customer != null) {
            String oldLevel = customer.getLevel();
            customer.setLevel(newLevel);
            customerRepository.save(customer);
            System.out.println("客户 " + customer.getName() + " 等级从 " + oldLevel + " 更新为 " + newLevel);
            this.notifyObserver(customer);
        }
        else {
            System.out.println("客户 " + customerId + " 未找到。");
        }

        System.out.println("---客户等级升级完成---\n");
    }

    public void addCustomer(Customer customer) {
        System.out.println("\n--- 添加新客户 ---");
        customerRepository.save(customer);
        System.out.println("新客户 " + customer.getName() + "已添加。");
        this.notifyObserver(customer);
        System.out.println("--- 新客户添加完成 ---\n");
    }
}
  • 作为具体主题, 维护一个 CustomerObserver 列表, 并在客户信息发生变化时, 调用 notifyObserver 方法通知所有注册的观察者执行各自的更新逻辑, 通知维护列表数据的新增及删除。
  • 作为外观类, 提供了 updateCustomerLevel(更新客户等级, 并在更新后通知观察者) 和 addCustomer(添加新客户, 并在添加后通知观察者) 方法, 简化客户端的调用逻辑。
测试类
ini 复制代码
public class CustomerTest {

    @Test
    public void test_updateCustomerLevel() {

        CustomerServiceFacade customerServiceFacade = new CustomerServiceFacade();

        EmailService emailService = new EmailService();
        MarketingService marketingService = new MarketingService();


        CustomerObserver emailObserver = new EmailNotificationObserver(emailService);
        CustomerObserver marketingObserver = new MarketingSyncObserver(marketingService);
        CustomerObserver alertObserver = new CustomerServiceAlertObserver();

        customerServiceFacade.addObserver(emailObserver);
        customerServiceFacade.addObserver(marketingObserver);
        customerServiceFacade.addObserver(alertObserver);

        customerServiceFacade.updateCustomerLevel("C001", "VIP");
        Customer newCustomer = new Customer("C002", "李四", "alice@example.com", "普通会员");
        customerServiceFacade.addCustomer(newCustomer);

        customerServiceFacade.removeObserver(marketingObserver);

        customerServiceFacade.updateCustomerLevel("C001", "VIP++");

    }
}
执行结果
sql 复制代码
观察者已添加: EmailNotificationObserver
观察者已添加: MarketingSyncObserver
观察者已添加: CustomerServiceAlertObserver

--- 开始更新客户等级 ---
客户仓库: 查找客户ID -> C001
客户仓库: 保存客户信息 -> id ='C001', name='张三', email='test@example.com', level='VIP'
客户 张三 等级从 Normal 更新为 VIP
通知所有观察者关于客户 C001 的更新...
邮件服务: 发送邮件到 test@example.com, 主题: 恭喜您升级为VIP会员!, 内容: 亲爱的 张三, 恭喜您已成功升级为VIP会员, 享受更多专属权益!
营销服务: 更新客户 C001 的营销标签为 VIP
客户提醒: 客户 张三 (ID: C001) 等级更新为 VIP, 请关注。
---客户等级升级完成---


--- 添加新客户 ---
客户仓库: 保存客户信息 -> id ='C002', name='李四', email='alice@example.com', level='普通会员'
新客户 李四已添加。
通知所有观察者关于客户 C002 的更新...
营销服务: 更新客户 C002 的营销标签为 普通会员
客户提醒: 客户 李四 (ID: C002) 等级更新为 普通会员, 请关注。
--- 新客户添加完成 ---

观察者已移除: MarketingSyncObserver

--- 开始更新客户等级 ---
客户仓库: 查找客户ID -> C001
客户仓库: 保存客户信息 -> id ='C001', name='张三', email='test@example.com', level='VIP++'
客户 张三 等级从 Normal 更新为 VIP++
通知所有观察者关于客户 C001 的更新...
客户提醒: 客户 张三 (ID: C001) 等级更新为 VIP++, 请关注。
---客户等级升级完成---

Process finished with exit code 0

组合优势

  • 外观模式隐藏了客户信息更新和后续处理的复杂性, 客户端只需通过外观类的统一接口进行操作, 无需关心内部的子系统调用和事件通知机制。
  • 外观模式负责简化接口和协调子系统, 观察者模式负责处理事件通知和多方响应,提供代码的清晰度和可维护性。
相关推荐
豌豆花下猫2 小时前
Python 潮流周刊#119:Google 停止开发 Pytype!
后端·python·ai
龙卷风04052 小时前
SpringAI调用第三方模型增加自定义请求参数
java·后端
Aurora_NeAr2 小时前
对比Java学习Go——函数、集合和OOP
后端
UnnamedOrange2 小时前
有来前后端部署
前端·后端
Aurora_NeAr2 小时前
Golang并发编程及其高级特性
后端·go
陈随易4 小时前
适合中国宝宝的AI编程神器,文心快码
前端·后端·node.js
毕设源码-朱学姐4 小时前
【开题答辩全过程】以 _基于SpringBoot技术的“树洞”心理咨询服务平台的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
阑梦清川4 小时前
AI编程实战记录贴2/100,关于Github提交代码失败的思考
后端