设计模式——外观设计模式(结构型)

0 阅读11分钟

摘要

本文介绍了外观设计模式,它是一种结构型设计模式,通过引入一个外观类来封装复杂子系统的调用细节,对外提供简单统一的接口。文中通过生活类比、关键角色介绍、使用场景分析以及结构说明等方面对这一模式进行了全面阐述,还涉及了实现方式、适合场景、实战示例和相关思考,有助于读者深入理解外观设计模式的原理和应用。

1. 外观设计模式定义

为复杂子系统提供一个统一的高层接口,使得子系统更易使用。外观模式通过引入一个“外观类(Facade)”,封装内部子系统的调用细节,对外暴露一个简单、统一的接口,隐藏系统的复杂性

1.1. 📦 举个通俗例子(生活类比):

点外卖 = 外观模式:

你使用美团 App 点外卖,只用点菜、下单,不需要关心

  • 餐厅是否接单(子系统 A)
  • 骑手怎么接单(子系统 B)
  • 结算怎么走账(子系统 C)

这就是“外观类”统一封装的行为。

1.2. ✅ 关键角色:

角色说明
Facade(外观)高层接口,封装子系统的复杂逻辑,对外提供简洁接口。
SubSystem(子系统)一组类或模块,完成具体业务逻辑。外观类内部会协调调用它们。
Client(客户端)只依赖外观类,屏蔽了对内部子系统的直接访问

1.3. ✅ 使用场景:

  • 系统结构复杂,希望对外提供简化接口;
  • 多个系统或模块集成,希望统一接入方式;
  • 用于分层架构(如 Controller -> Service -> Facade -> Subsystem);
  • 旧系统封装重构:用外观封装老接口,屏蔽调用细节。

2. 外观设计模式结构

外观模式包含如下角色:

  • Facade: 外观角色
  • SubSystem:子系统角色

2.1. 外观设计模式类图

2.2. 外观设计模式时序图

3. 外观设计模式实现方式

外观设计模式(Facade Pattern)的实现方式非常清晰明确:通过封装多个子系统的复杂调用逻辑,统一对外提供一个简单接口。

3.1. ✅ 实现步骤(标准实现方式)

3.1.1. 🔹 步骤 1:定义多个子系统(SubSystem)类

@Service
public class RiskScoreService {
    public int getRiskScore(String userId) {
        System.out.println("计算用户风险分...");
        return 75;
    }
}

@Service
public class BlacklistService {
    public boolean isBlacklisted(String userId) {
        System.out.println("检查黑名单...");
        return false;
    }
}

@Service
public class CreditService {
    public int getCreditLimit(String userId) {
        System.out.println("获取信用额度...");
        return 5000;
    }
}

3.1.2. 🔹 步骤 2:定义外观类(Facade)

@Servcie
public class RiskFacade {

    @Autowired
    private final RiskScoreService riskScoreService;
    
    @Autowired
    private final BlacklistService blacklistService;
    
    @Autowired
    private final CreditService creditService;

    public void assessUserRisk(String userId) {
        System.out.println("开始用户风控评估...");

        if (blacklistService.isBlacklisted(userId)) {
            System.out.println("用户被拉黑,拒绝服务!");
            return;
        }

        int score = riskScoreService.getRiskScore(userId);
        int credit = creditService.getCreditLimit(userId);

        System.out.println("风控评估完成,风险分:" + score + ",信用额度:" + credit);
    }
}

3.1.3. 🔹 步骤 3:客户端只使用 Facade,不关心子系统

public class Client {
    public static void main(String[] args) {
        RiskFacade facade = new RiskFacade();
        facade.assessUserRisk("user123");
    }
}

3.2. ✅ 在 Spring 项目中的实现方式

如果项目使用 Spring 框架,我们通常会将子系统类标记为 @Service ,将外观类作为一个统一入口暴露:

3.2.1. 🔹 子系统类(Spring Bean)

@Service
public class RiskScoreService { ... }

@Service
public class BlacklistService { ... }

@Service
public class CreditService { ... }

3.2.2. 🔹 外观类作为统一接口

@Servcie
public class RiskFacade {

    @Autowired
    private RiskScoreService riskScoreService;

    @Autowired
    private BlacklistService blacklistService;

    @Autowired
    private CreditService creditService;

    public void assess(String userId) {
        // 同上,统一调用子服务
    }
}

3.2.3. 🔹 Controller 层只依赖外观类

@RestController
@RequestMapping("/risk")
public class RiskController {

    @Autowired
    private RiskFacade riskFacade;

    @GetMapping("/assess")
    public String assess(@RequestParam String userId) {
        riskFacade.assess(userId);
        return "评估完成";
    }
}

3.3. ✅ 总结:外观模式实现要点

步骤内容
把多个子系统服务拆分成独立类
建立一个 Facade类,对外暴露统一方法
客户端只与 Facade类交互
④(Spring)把子系统类交给 Spring 管理,外观类通过注入协调调用

4. 外观设计模式适合场景

4.1. ✅ 适合使用外观设计模式的场景

场景说明
系统结构复杂系统由多个子系统组成,接口调用复杂,客户端需要简化调用流程。
统一访问入口需要为多个子系统提供一个统一的接口,客户端只需调用这个接口。
分层架构设计在分层架构中,用外观层屏蔽底层子系统的实现细节,降低耦合度。
系统重构与迁移需要将旧系统接口封装,兼容老旧代码,逐步迁移到新系统。
多子系统协调需要协调多个子系统的调用顺序或组合调用逻辑,外观类负责协调。

4.2. ❌ 不适合使用外观设计模式的场景

场景原因
系统简单业务流程简单,接口调用不复杂,使用外观反而增加额外层次和复杂度。
单一功能模块只涉及一个功能模块,没有必要额外封装统一接口。
频繁变动接口子系统接口频繁改变,外观层也需频繁修改,维护成本高。
业务高度耦合业务流程需要客户端灵活控制子系统内部调用,外观隐藏细节不合适。

5. 外观设计模式实战示例

下面是一个金融风控场景下的外观设计模式实战示例,演示如何用Spring管理所有对象,并且使用注解方式注入,避免构造函数注入,方便集成和维护。

5.1. 项目背景

风控系统需要对用户进行风险评估,涉及多个子系统服务:

  • 黑名单查询服务(BlacklistService)
  • 风险分数计算服务(RiskScoreService)
  • 信用额度服务(CreditService)

通过外观模式(RiskFacade)统一暴露给业务调用层,隐藏各子系统复杂调用。

5.2. 子系统服务类

@Service
public class BlacklistService {

    public boolean isBlacklisted(String userId) {
        System.out.println("检查用户是否在黑名单中...");
        // 模拟黑名单检查逻辑
        return "blacklistedUser".equals(userId);
    }
}

@Service
public class RiskScoreService {

    public int calculateRiskScore(String userId) {
        System.out.println("计算用户风险分数...");
        // 模拟风险分数计算
        return 80;
    }
}

@Service
public class CreditService {

    public int getCreditLimit(String userId) {
        System.out.println("获取用户信用额度...");
        // 模拟信用额度查询
        return 10000;
    }
}

5.3. 外观类

@Component
public class RiskFacade {

    @Autowired
    private BlacklistService blacklistService;

    @Autowired
    private RiskScoreService riskScoreService;

    @Autowired
    private CreditService creditService;

    public void assessUserRisk(String userId) {
        System.out.println("=== 开始风控评估 ===");

        if (blacklistService.isBlacklisted(userId)) {
            System.out.println("用户 " + userId + " 在黑名单中,拒绝服务!");
            return;
        }

        int riskScore = riskScoreService.calculateRiskScore(userId);
        int creditLimit = creditService.getCreditLimit(userId);

        System.out.println("用户 " + userId + " 风险分数: " + riskScore);
        System.out.println("用户 " + userId + " 信用额度: " + creditLimit);
        System.out.println("=== 评估结束 ===");
    }
}

5.4. Controller 层示例

@RestController
@RequestMapping("/risk")
public class RiskController {

    @Autowired
    private RiskFacade riskFacade;

    @GetMapping("/assess")
    public String assessRisk(@RequestParam String userId) {
        riskFacade.assessUserRisk(userId);
        return "风控评估完成";
    }
}

说明

  • 所有类都由Spring管理,使用 @Service@Component 注解。
  • 外观类 RiskFacade 注入所有子系统服务,作为统一调用入口。
  • 业务层(Controller)只调用外观类接口,避免直接依赖多个子系统。
  • 避免构造函数注入,使用 @Autowired 注解实现自动注入,符合你的要求。

6. 外观设计模式思考

6.1. 门面设计设计模式和DDD中Facade设计区别

6.1.1. 门面设计模式(Facade Pattern)

  • 定义:Facade 是一种结构型设计模式,用于为复杂的子系统提供一个简化的统一接口。它屏蔽了系统的复杂性,客户端通过门面类与子系统交互,而无需直接了解子系统的实现细节。
  • 关注点:简化接口,降低客户端与子系统之间的耦合。
  • 典型用途
    • 为一个复杂系统提供统一的入口。
    • 隐藏子系统的内部复杂逻辑。
    • 提高客户端调用的便利性。

6.1.2. DDD 中的 Facade

  • 定义:在领域驱动设计中,Facade 是一个用于协调多个领域对象领域服务的接口或类。它通常用于应用层,作为应用服务的一部分,负责将客户端的请求转化为对领域层的调用。
  • 关注点:隔离应用层与领域层,简化应用层与外部系统(如 UI、接口调用等)的交互。
  • 典型用途
    • 在应用层对外暴露接口。
    • 封装复杂的领域操作,协调多个领域对象和领域服务。
    • 承载用例(Use Case)的实现逻辑。

6.1.3. 核心区别

维度门面设计模式(Facade Pattern)DDD 中的 Facade
目的为复杂子系统提供一个统一、简化的接口,屏蔽系统内部实现细节。为外部系统(如 UI 层、API 层)提供对领域层的调用接口。
适用范围用于封装技术组件(子系统、模块、服务)。用于封装领域逻辑,暴露领域行为。
位置通常在技术实现层,用于协调多个技术模块。通常在应用层,调用领域层服务或聚合根。
关注点简化客户端调用,隐藏子系统复杂性。承载用例逻辑,协调领域对象和服务,实现业务需求。
是否直接操作领域通常不直接操作领域对象,只封装系统内部的模块调用。直接操作领域对象、聚合根、领域服务等。

6.2. 门面设计模式(Facade Pattern)的设计思想与 Application 层 的职责相似

6.2.1. Application层与门面模式(Facade Pattern)

Application 层(DDD 中的角色)

  • 主要职责
    • 提供用例逻辑(Use Case)服务。
    • 负责协调领域层(Domain Layer)的多个领域对象、领域服务和聚合根。
    • 为外部系统(如 API 层、UI 层等)提供统一的调用接口。
    • 不包含业务逻辑,业务逻辑属于领域层,它仅负责调度领域逻辑
  • 核心思想
    • 简化外部调用(UI 层或 API 层)对复杂领域逻辑的访问。
    • 将应用层与领域层隔离,保证领域层专注于业务规则,而应用层处理系统操作的组合与流程。

门面模式(Facade Pattern)

  • 主要职责
    • 为子系统提供一个统一接口,屏蔽系统内部的复杂性。
    • 将多个子系统或模块的调用逻辑封装在一个类中,外部调用方无需了解子系统的细节。
    • 简化客户端调用,降低外部代码与子系统的耦合度。
  • 核心思想
    • 提供一个简化的、高层次的接口来调用内部复杂的逻辑或子系统。

6.2.2. Application 层和门面模式的相似性

维度Application 层门面模式(Facade Pattern)
主要职责调用领域层的对象和服务,为外部系统提供统一的调用接口。调用子系统的服务,为客户端提供简化的调用入口。
目标简化外部系统对复杂领域逻辑的调用,协调领域服务和对象。隐藏子系统的复杂性,为客户端提供简化的接口。
隐藏复杂性隐藏领域层内部对象之间的交互细节。隐藏子系统之间的交互细节。
调用方外部系统(UI 层、API 层等)。客户端或其他模块。
实现的粒度业务用例为单位,封装一个完整的应用逻辑。技术组件为单位,封装多个模块或服务的调用逻辑。

结论两者都承担了“简化复杂性、统一接口”的职责,但 Application 层更专注于领域逻辑的编排和业务用例,而门面模式更关注技术子系统的整合和封装。

6.3. 项目提供RPC 服务接口设计是不是属于的门面设计模式?

是的,可以认为属于门面设计模式的应用。门面模式(Facade Pattern)定义:为子系统中的一组复杂接口提供一个统一的高层接口,使子系统更易使用。

Spring 中 RPC 服务接口的特点(比如基于 Dubbo、gRPC、Spring Cloud):

特性说明
📦 对外暴露服务接口Controller 不再是主角,RPC 接口才是系统“对外的门面”
⚙️ 封装多个底层业务服务(Service、DAO、组件等)对外提供统一调用入口
🔐 对调用者隐藏实现细节调用方只知道接口,内部逻辑对其不可见
🔌 提供远程访问能力一般用于微服务、分布式系统间通信

这与门面模式的核心思想高度一致:对复杂子系统提供统一、简洁、高层次的访问接口。示例场景:

假设你有一个贷款审批系统,下游调用方只需调用如下 RPC 接口:

public interface LoanApprovalRpcService {
    ApprovalResult approveLoan(LoanApplicationDTO application);
}

而这个接口内部其实会:

  • 校验申请数据
  • 调用风控系统
  • 查询信用评分
  • 写入审批日志
  • 通知第三方平台

这时你暴露的这个 RPC 接口就非常标准地扮演了“门面”角色

  • 对外隐藏了所有复杂的逻辑
  • 调用者只需要关心一个方法:approveLoan(...)

博文参考