跳到主要内容

外观模式(Facade Pattern)

序言

外观模式(Facade Pattern)是一种结构型设计模式,它提供了一个简单的接口,隐藏了一个系统更大和更复杂的一部分的复杂性,从而使系统更易于使用。

外观模式的核心思想是为子系统提供一个统一的接口,以便客户端可以更容易地使用这些子系统。通过外观模式,客户端可以通过一个简单的接口来访问系统的各种功能,而不需要了解系统内部的复杂逻辑和实现细节。

这种模式在许多软件开发场景中都有实际应用,特别是在需要简化复杂系统接口的情况下,外观模式可以帮助减少系统之间的耦合,并提供更清晰的界面供客户端使用。

定义

Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level
interface that makes the subsystem easier to use.

为一个子系统中的一系列接口提供一个统一的接口。外观定义了一个更高级别的接口以便子系统更容易使用。

结构

想象一下系统中有一个添加客户的功能,在添加客户前需要验证客户的实名认证信息,而在客户保存后需要将客户信息推送到三方财务系统(如NCC)、电子签署系统。

而这些功能都分布在多个类中。

如果我们需要实现一个客户添加功能,需要同时调用4个业务层的接口进行处理。而系统中存在多个客户创建的入口,且部分入口嵌入在其他的业务功能中。那我们就需要在每个创建客户的入口都要关注这个创建客户的流程,且如果涉及到多个开发人员进行开发的话,每个开发人员也都需要清楚这套客户的创建流程。

那有没有办法简化这个功能呢?那就是外观模式。我们提供一个客户创建的外观类,提供一个客户创建的接口。通过这个接口,我们可以将创建客户的具体细节隐藏,使得调用方只需要关心这个接口,而无需关心创建客户的具体细节。

代码实现

先创建基础的Model类和Dto类

@Data
public class Customer {
private String customerId;
private String customerName;
private String phoneNumber;
private String idCardNo;
}
@Data
public class CustomerDto {
private String customerName;
private String phoneNumber;
private String idCardNo;
}
java

然后是各个业务接口。业务接口中并不实现具体业务,仅做简单打印。

public class RealNameCertificationService {

boolean certify(String name, String idCardNo) {
System.out.println("客户姓名:" + name + ",身份证号:" + idCardNo + ",实名认证通过");
return true;
}
}

public class CustomerSaveService {

Customer save(CustomerDto dto) {
Customer customer = new Customer();
BeanUtils.copyProperties(dto, customer);
// 请不要在生产系统中使用这种方式生成ID
customer.setCustomerId("" + System.currentTimeMillis());
// customerMapper.insert(customer)
System.out.println("客户:" + customer.getCustomerName() + "已保存");
return customer;
}
}

public class NccCustomerSyncService {

public void create(Customer customer) {
System.out.println("已将客户信息同步至NCC");
}
}

public class ESignCustomerSyncService {
public void create(Customer customer) {
System.out.println("已将客户信息同步至电子签系统");
}
}
java

下面实现外观类,在外观类中实现了具体的客户创建过程的所有细节。在创建之前先做客户的实名认证校验,然后保存客户信息。保存客户信息后将客户同步至NCC和电子签系统。NCC和电子签系统属于第三方系统,所以要求三方系统的接口处理结果不能影响主流程。也即无论NCC和电子签系统的客户同步是否成功,都不能影响客户的保存。同时NCC和电子签系统的同步结果不互相影响。

public class CustomerCreateFacade {
private final RealNameCertificationService realNameCertificationService;
private final CustomerSaveService customerSaveService;

private final NccCustomerSyncService nccCustomerSyncService;

private final ESignCustomerSyncService eSignCustomerSyncService;

public CustomerCreateFacade(RealNameCertificationService realNameCertificationService, CustomerSaveService customerSaveService, NccCustomerSyncService nccCustomerSyncService, ESignCustomerSyncService eSignCustomerSyncService) {
this.realNameCertificationService = realNameCertificationService;
this.customerSaveService = customerSaveService;
this.nccCustomerSyncService = nccCustomerSyncService;
this.eSignCustomerSyncService = eSignCustomerSyncService;
}

public void create(CustomerDto dto) {
boolean checkResult = this.realNameCertificationService.certify(dto.getCustomerName(), dto.getIdCardNo());
if (!checkResult) {
throw new RuntimeException("客户实名认证未通过");
}
Customer customer = this.customerSaveService.save(dto);

// 开始同步信息到NCC和电子签,两者互不影响。无论哪一方失败都不影响另外一方。两者是否执行成功,不影响客户信息的保存。

try {
this.nccCustomerSyncService.create(customer);
} catch (Exception e) {
System.out.println("NCC同步失败");
}

try {
this.eSignCustomerSyncService.create(customer);
} catch (Exception e) {
System.out.println("电子签系统同步失败");
}

}
}
java

使用门面类

public class Main {

public static void main(String[] args) {
RealNameCertificationService realNameCertificationService = new RealNameCertificationService();
CustomerSaveService customerSaveService = new CustomerSaveService();
NccCustomerSyncService nccCustomerSyncService = new NccCustomerSyncService();
ESignCustomerSyncService eSignCustomerSyncService = new ESignCustomerSyncService();

CustomerCreateFacade customerCreateFacade = new CustomerCreateFacade(realNameCertificationService, customerSaveService, nccCustomerSyncService, eSignCustomerSyncService);

CustomerDto customerDto = new CustomerDto();
customerDto.setCustomerName("张三");
customerDto.setPhoneNumber("13000000000");
customerDto.setCustomerName("410521120001010000");

customerCreateFacade.create(customerDto);
}
}
java

输出结果

客户姓名:410521120001010000,身份证号:null,实名认证通过
客户:410521120001010000已保存
已将客户信息同步至NCC
已将客户信息同步至电子签系统

现在系统中无论哪里需要创建客户,都只需要调用外观类提供的创建客户接口即可,使用方只需要关注接口参数即可,对于内部的创建细节,可以不用了解。

在开源框架中的应用

Spring框架中的ApplicationContext可以看作是一个门面,它提供了一个统一的接口来管理Spring容器中的各个组件,隐藏了底层组件的复杂性。

Hibernate框架中的SessionFactory可以看作是一个门面,它提供了一个统一的接口来管理持久化对象的生命周期和数据库访问,隐藏了底层数据库访问的复杂性。

何时使用

外观模式通常在以下情况下使用:

  • 简化复杂系统: 当系统包含许多复杂的子系统或组件时,外观模式可以提供一个简单的接口,使客户端更容易地使用系统的功能,而不需要了解系统内部的复杂逻辑。
  • 解耦子系统: 外观模式可以帮助减少系统内部各个子系统之间的耦合度,因为客户端只需要与外观接口交互,而不需要直接与各个子系统交互。
  • 遗留系统整合: 在需要整合遗留系统或第三方库时,外观模式可以提供一个统一的接口,使得整合过程更加简单和清晰。
  • 客户端与系统解耦: 外观模式可以帮助客户端与系统解耦,使得系统内部的变化不会影响客户端的代码,从而提高了系统的灵活性和可维护性。

外观模式适用于需要简化复杂系统、降低耦合度、提供统一接口的情况。

与其他设计模式的联系

外观模式与其他设计模式有一些联系,特别是与适配器模式和中介者模式有一些相似之处。

  • 外观模式(Facade Pattern): 外观模式的主要目的是简化接口。它通过创建一个统一的高层接口来隐藏系统的复杂性,使得子系统更容易使用。外观模式通常用于提供一个全局的访问点,控制对系统中一组接口的访问,从而减少用户需要直接交互的接口数量。这种模式适用于当一个系统有多个类或组件,且它们之间的交互复杂时,外观模式可以提供一个简化的接口来统一这些交互。

  • 适配器模式(Adapter Pattern): 外观模式和适配器模式都涉及到对接口的转换和包装。适配器模式的目的是使两个不兼容的接口能够合作。它通过创建一个中间层,即适配器,来实现这一点。适配器模式有两种实现方式:对象适配器和类适配器。对象适配器使用组合,可以适配一个类及其任何子类;类适配器则通过继承来实现适配,不需要重新实现整个被适配者。这种模式适用于当需要让现有的类与其他不兼容的类一起工作时,可以通过适配器来转换它们的接口。

  • 中介者模式(Mediator Pattern): 外观模式和中介者模式都涉及到对系统中各个组件之间的交互进行管理。中介者模式的目的是通过一个中介对象来封装一系列对象之间的交互。它允许各个对象之间不直接通信,而是通过中介者来进行交互,这样可以减少对象之间的直接依赖,降低系统的耦合度。在中介者模式中,可以更容易地独立改变和复用对象之间的交互,因为所有的交互都通过中介者进行。

外观模式主要是为了简化接口;适配器模式则是为了解决接口不兼容的问题;而中介者模式则是为了控制和减少对象之间的直接交互,增强系统的可扩展性。在实际的软件设计中,根据具体的需求场景选择合适的模式是非常重要的。

总结

外观模式是一种使用频率极高的设计模式,它通过提供一个统一的高层接口来隐藏系统的复杂性。这种模式在多层次的系统结构中尤为重要,它作为每层的入口,简化了层间的调用,提高了系统的可用性。

在外观模式中,一个外观类通常包含对多个子系统对象的引用,客户端通过外观类提供的简化接口与子系统进行交互,而不需要直接与子系统中的多个对象交互。这样,外观类就承担了客户端与子系统之间的通信中介者的角色,降低了客户端与子系统之间的耦合度。

外观模式是一种有效的设计模式,它通过简化接口和降低耦合度,有助于构建清晰、易于维护和扩展的系统结构。