写在前面
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;
}
}
}
- 外观模式的核心类, 它聚合了
InventoryService
、PaymentService
、NotificationService
的实例, 并提供了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
组合优势
- 外观模式隐藏了客户信息更新和后续处理的复杂性, 客户端只需通过外观类的统一接口进行操作, 无需关心内部的子系统调用和事件通知机制。
- 外观模式负责简化接口和协调子系统, 观察者模式负责处理事件通知和多方响应,提供代码的清晰度和可维护性。