摘要
本文介绍了外观设计模式,这是一种通过简单接口封装复杂系统的设计模式。它简化了客户端与子系统之间的交互,降低了耦合度,并提供了统一的调用接口。文章还探讨了该模式的优缺点,并提供了类图实现和使用场景。
1. 外观设计模式是什么
外观模式的核心思想:用一个简单的接口来封装一个复杂的系统,使这个系统更容易使用。与DDD思想中application 有相似之处。
1.1. 外观设计模式作用
- 简化访问:门面模式通过提供一个简单的接口,将复杂的子系统封装起来。外部不需要了解子系统的内部逻辑,只需要通过门面类与子系统交互。
- 降低耦合:门面模式可以减少外部代码与子系统之间的依赖性。如果子系统发生了变化,只需要调整门面类即可,外部代码无需修改。
1.2. 外观设计模式优缺点
优点:
- 实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端。
- 简化了客户端对子系统的使用难度,客户端(用户)无须关心子系统的具体实现方式,而只需要和外观进行交互即可。
- 为不同的用户提供了统一的调用接口,方便了系统的管理和维护。
缺点:
- 因为统一了调用的接口,降低了系统功能的灵活性。
2. 外观设计模类图实现
2.1. 外观设计类模型
外观模式是最简单的设计模式之一,只有两个角色。
- 外观角色(Facade): 为子系统封装统一的对外接口,如同子系统的一个门面。这个类一般不负责具体的业务逻辑,只是一个委托类,具体的业务逻辑由子系统完成。
- 子系统(SubSystem): 由多个类组成的具有某一特定功能的子系统。可以是第三方库,也可以是自己的基础库,还可能是一个子服务,为整个系统提供特定的功能或服务。
在软件的层次化结构设计中,可以使用外观模式来定义每一层系统的调用接口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度。这时就会有如下这样的层次结构图:
2.2. 门面设计设计模式和DDD中Facade设计区别
2.2.1. 门面设计模式(Facade Pattern)
定义:Facade 是一种结构型设计模式,用于为复杂的子系统提供一个简化的统一接口。它屏蔽了系统的复杂性,客户端通过门面类与子系统交互,而无需直接了解子系统的实现细节。
关注点:简化接口,降低客户端与子系统之间的耦合。
典型用途:
- 为一个复杂系统提供统一的入口。
- 隐藏子系统的内部复杂逻辑。
- 提高客户端调用的便利性。
2.2.2. DDD 中的 Facade层
定义:在领域驱动设计中,Facade 是一个用于协调多个领域对象或领域服务的接口或类。它通常用于应用层,作为应用服务的一部分,负责将客户端的请求转化为对领域层的调用。
关注点:隔离应用层与领域层,简化应用层与外部系统(如 UI、接口调用等)的交互。
典型用途:
- 在应用层对外暴露接口。
- 封装复杂的领域操作,协调多个领域对象和领域服务。
- 承载用例(Use Case)的实现逻辑。
2.2.3. 核心区别
维度 | 门面设计模式(Facade Pattern) | DDD 中的 Facade |
目的 | 为复杂子系统提供一个统一、简化的接口,屏蔽系统内部实现细节。 | 为外部系统(如 UI 层、API 层)提供对领域层的调用接口。 |
适用范围 | 用于封装技术组件(子系统、模块、服务)。 | 用于封装领域逻辑,暴露领域行为。 |
位置 | 通常在技术实现层,用于协调多个技术模块。 | 通常在应用层,调用领域层服务或聚合根。 |
关注点 | 简化客户端调用,隐藏子系统复杂性。 | 承载用例逻辑,协调领域对象和服务,实现业务需求。 |
是否直接操作领域 | 通常不直接操作领域对象,只封装系统内部的模块调用。 | 直接操作领域对象、聚合根、领域服务等。 |
2.3. 门面设计模式的设计思想与Application层的职责相似
2.3.1. Application层与门面模式(Facade Pattern)
Application 层(DDD 中的角色)
主要职责:
- 提供用例逻辑(Use Case)服务。
- 负责协调领域层(Domain Layer)的多个领域对象、领域服务和聚合根。
- 为外部系统(如 API 层、UI 层等)提供统一的调用接口。
- 不包含业务逻辑,业务逻辑属于领域层,它仅负责调度领域逻辑。
核心思想:
- 简化外部调用(UI 层或 API 层)对复杂领域逻辑的访问。
- 将应用层与领域层隔离,保证领域层专注于业务规则,而应用层处理系统操作的组合与流程。
门面模式(Facade Pattern)
主要职责:
- 为子系统提供一个统一接口,屏蔽系统内部的复杂性。
- 将多个子系统或模块的调用逻辑封装在一个类中,外部调用方无需了解子系统的细节。
- 简化客户端调用,降低外部代码与子系统的耦合度。
- 核心思想:
- 提供一个简化的、高层次的接口来调用内部复杂的逻辑或子系统。
2.3.2. Application 层和门面模式的相似性
维度 | Application 层 | 门面模式(Facade Pattern) |
主要职责 | 调用领域层的对象和服务,为外部系统提供统一的调用接口。 | 调用子系统的服务,为客户端提供简化的调用入口。 |
目标 | 简化外部系统对复杂领域逻辑的调用,协调领域服务和对象。 | 隐藏子系统的复杂性,为客户端提供简化的接口。 |
隐藏复杂性 | 隐藏领域层内部对象之间的交互细节。 | 隐藏子系统之间的交互细节。 |
调用方 | 外部系统(UI 层、API 层等)。 | 客户端或其他模块。 |
实现的粒度 | 以业务用例为单位,封装一个完整的应用逻辑。 | 以技术组件为单位,封装多个模块或服务的调用逻辑。 |
结论:两者都承担了“简化复杂性、统一接口”的职责,但 Application 层更专注于领域逻辑的编排和业务用例,而门面模式更关注技术子系统的整合和封装。
3. 外观设计模式使用场景
3.1. 为复杂系统提供简化接口
场景:当一个系统由多个模块、子系统组成,客户端需要与这些模块进行交互时,如果直接调用底层模块,会导致客户端逻辑复杂且高度耦合。
示例:视频转码系统包含解码模块、转码模块、压缩模块等多个子系统,通过一个 VideoProcessingFacade
统一接口简化调用流程。
@Service
public class VideoProcessingFacade {
@Autowired
private Decoder decoder;
@Autowired
private Transcoder transcoder;
@Autowired
private Compressor compressor;
public void processVideo(String filePath) {
decoder.decode(filePath);
transcoder.transcode();
compressor.compress();
}
}
应用场景:客户端只需要调用 processVideo
,无需关心底层模块的具体调用顺序和逻辑。
3.2. 隔离与第三方库的耦合
场景:当项目需要调用第三方库(如支付网关、短信服务、邮件服务等)时,如果直接使用其 API,可能会导致代码和第三方库耦合,增加维护成本。使用门面模式可以隔离这种耦合。
示例:支付服务集成不同的支付网关(如 PayPal、Stripe、支付宝)。
@Service
public class PaymentFacade {
@Autowired
private PayPalService payPalService;
@Autowired
private StripeService stripeService;
public void payWithPayPal(String account, double amount) {
payPalService.processPayment(account, amount);
}
public void payWithStripe(String account, double amount) {
stripeService.processPayment(account, amount);
}
}
应用场景:客户端只需要知道 PaymentFacade
提供的支付接口,而无需了解具体的支付网关实现。
3.3. 封装遗留系统
场景:当需要对接一个遗留系统(Legacy System),但不想直接暴露遗留系统复杂或低效的接口时,可以使用门面模式封装其调用。
示例:一个遗留的客户信息管理系统接口复杂,可以通过门面模式提供统一的接口。
@Service
public class CustomerServiceFacade {
@Autowired
private LegacyCustomerService legacyService;
public Customer getCustomerDetails(String customerId) {
return legacyService.getCustomerData(customerId);
}
}
应用场景:客户端通过门面接口获取客户信息,无需直接与遗留系统交互。
3.4. 提供多个子系统的统一入口
- 场景:在系统中有多个子系统需要协调工作,且需要对外暴露一个简化的统一接口。
- 示例:智能家居系统包含灯光控制、空调控制、音响控制等模块,可以通过一个
SmartHomeFacade
提供统一入口。
@Service
public class SmartHomeFacade {
@Autowired
private LightController lightController;
@Autowired
private AirConditionerController airConditionerController;
@Autowired
private MusicController musicController;
public void startEveningMode() {
lightController.dimLights();
airConditionerController.setTemperature(22);
musicController.playRelaxingMusic();
}
}
3.5. 简化复杂工作流
场景:当一个复杂的业务流程需要多个步骤完成(如订单处理、物流系统对接等),可以使用门面模式封装工作流的实现。
示例:订单处理系统需要完成库存检查、支付处理、物流创建等步骤。
@Service
public class OrderFacade {
@Autowired
private InventoryService inventoryService;
@Autowired
private PaymentService paymentService;
@Autowired
private ShippingService shippingService;
public void processOrder(String productId, String userId, double amount) {
if (inventoryService.checkStock(productId)) {
if (paymentService.processPayment(userId, amount)) {
shippingService.createShipment(productId, userId);
}
}
}
}
应用场景:简化订单处理流程,客户端只需调用 processOrder
方法即可。
3.6. 提供接口的多版本支持
场景:系统需要对外暴露多个版本的接口时,可以使用门面模式将不同版本的实现封装在同一个接口中。
示例:API 网关根据客户端请求调用不同版本的服务实现。
@Service
public class ApiGatewayFacade {
@Autowired
private V1Service v1Service;
@Autowired
private V2Service v2Service;
public void callApi(String version, String request) {
if ("v1".equals(version)) {
v1Service.handleRequest(request);
} else if ("v2".equals(version)) {
v2Service.handleRequest(request);
}
}
}
应用场景:统一管理和调用不同版本的服务实现。
3.7. 统一对外暴露接口,隐藏底层实现
场景:当系统需要对外暴露一个简单的公共接口,但不希望外部用户了解底层实现细节时,可以使用门面模式。
示例:企业级服务平台(如 ERP 系统)封装内部模块,统一对外提供服务。
@Service
public class EnterpriseServiceFacade {
@Autowired
private FinanceService financeService;
@Autowired
private HRService hrService;
public void processPayroll() {
financeService.calculateSalaries();
hrService.updateEmployeeRecords();
}
}
应用场景:对外统一暴露企业服务,无需暴露各模块的具体实现。
3.8. 降低代码的维护复杂性
场景:当系统模块或服务之间的调用关系复杂,未来可能需要更换某些模块的实现时,通过门面模式可以降低修改代码时的风险。
示例:缓存系统封装 Redis 和 Memcached 的实现,后期替换时只需修改门面内部逻辑。
@Service
public class CacheFacade {
@Autowired
private RedisCache redisCache;
@Autowired
private MemcachedCache memcachedCache;
public void put(String key, String value) {
redisCache.put(key, value);
memcachedCache.put(key, value);
}
public String get(String key) {
return redisCache.get(key);
}
}
4. 外观设计模式示例
假设我们正在设计一个订单处理系统,包含以下子系统:
- 库存服务:检查库存是否足够。
- 支付服务:处理支付逻辑。
- 通知服务:在订单成功后发送通知。
客户端不需要直接与这些子系统交互,而是通过一个门面类来调用这些服务。
4.1. 外观设计模式实现
4.1.1. 子系统类
这些子系统类是具体的服务,它们各自完成自己的职责。
package com.example.subsystems;
import org.springframework.stereotype.Component;
// 库存服务
@Component
public class InventoryService {
public boolean checkStock(String productId) {
System.out.println("Checking stock for product: " + productId);
return true; // 假设库存充足
}
}
// 支付服务
@Component
public class PaymentService {
public boolean processPayment(String userId, double amount) {
System.out.println("Processing payment for user: " + userId + ", amount: " + amount);
return true; // 假设支付成功
}
}
// 通知服务
@Component
public class NotificationService {
public void sendNotification(String userId, String message) {
System.out.println("Sending notification to user: " + userId + ", message: " + message);
}
}
4.1.2. 门面类
门面类封装了对多个子系统的调用逻辑,对外提供一个统一的接口。
package com.example.facade;
import com.example.subsystems.InventoryService;
import com.example.subsystems.PaymentService;
import com.example.subsystems.NotificationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class OrderFacade {
@Autowired
private InventoryService inventoryService;
@Autowired
private PaymentService paymentService;
@Autowired
private NotificationService notificationService;
public boolean processOrder(String userId, String productId, double amount) {
// 1. 检查库存
if (!inventoryService.checkStock(productId)) {
System.out.println("Stock not sufficient for product: " + productId);
return false;
}
// 2. 处理支付
if (!paymentService.processPayment(userId, amount)) {
System.out.println("Payment failed for user: " + userId);
return false;
}
// 3. 发送通知
notificationService.sendNotification(userId, "Order for product " + productId + " has been successfully processed!");
System.out.println("Order processing completed successfully!");
return true;
}
}
4.1.3. 客户端
客户端只需要调用门面类的方法,而不需要关心子系统的具体实现。
package com.example.client;
import com.example.facade.OrderFacade;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class OrderClient implements CommandLineRunner {
@Autowired
private OrderFacade orderFacade;
@Override
public void run(String... args) {
// 模拟处理订单
String userId = "user123";
String productId = "product456";
double amount = 99.99;
boolean result = orderFacade.processOrder(userId, productId, amount);
if (result) {
System.out.println("Order processed successfully!");
} else {
System.out.println("Order processing failed!");
}
}
}
博文参考
《软件设计模式》