设计模式——Facade(门面)设计模式

摘要

本文介绍了外观设计模式,这是一种通过简单接口封装复杂系统的设计模式。它简化了客户端与子系统之间的交互,降低了耦合度,并提供了统一的调用接口。文章还探讨了该模式的优缺点,并提供了类图实现和使用场景。

1. 外观设计模式是什么

外观模式的核心思想:用一个简单的接口来封装一个复杂的系统,使这个系统更容易使用。与DDD思想中application 有相似之处。

1.1. 外观设计模式作用

  1. 简化访问:门面模式通过提供一个简单的接口,将复杂的子系统封装起来。外部不需要了解子系统的内部逻辑,只需要通过门面类与子系统交互。
  2. **降低耦合:**门面模式可以减少外部代码与子系统之间的依赖性。如果子系统发生了变化,只需要调整门面类即可,外部代码无需修改。

1.2. 外观设计模式优缺点

优点:

  • 实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端。
  • 简化了客户端对子系统的使用难度,客户端(用户)无须关心子系统的具体实现方式,而只需要和外观进行交互即可。
  • 为不同的用户提供了统一的调用接口,方便了系统的管理和维护。

缺点:

  • 因为统一了调用的接口,降低了系统功能的灵活性。

2. 外观设计模类图实现

2.1. 外观设计类模型

外观模式是最简单的设计模式之一,只有两个角色。

  1. 外观角色(Facade): 为子系统封装统一的对外接口,如同子系统的一个门面。这个类一般不负责具体的业务逻辑,只是一个委托类,具体的业务逻辑由子系统完成。
  2. 子系统(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. 外观设计模式示例

假设我们正在设计一个订单处理系统,包含以下子系统:

  1. 库存服务:检查库存是否足够。
  2. 支付服务:处理支付逻辑。
  3. 通知服务:在订单成功后发送通知。

客户端不需要直接与这些子系统交互,而是通过一个门面类来调用这些服务。

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!");
        }
    }
}

博文参考

《软件设计模式》

相关推荐
大圣数据星球2 小时前
Fluss 写入数据湖实战
大数据·设计模式·flink
思忖小下3 小时前
梳理你的思路(从OOP到架构设计)_设计模式Template Method模式
设计模式·模板方法模式·eit
思忖小下13 小时前
梳理你的思路(从OOP到架构设计)_简介设计模式
设计模式·架构·eit
liyinuo201716 小时前
嵌入式(单片机方向)面试题总结
嵌入式硬件·设计模式·面试·设计规范
aaasssdddd9618 小时前
C++的封装(十四):《设计模式》这本书
数据结构·c++·设计模式
T1an-118 小时前
设计模式之【观察者模式】
观察者模式·设计模式
思忖小下20 小时前
梳理你的思路(从OOP到架构设计)_设计模式Factory Method模式
设计模式·工厂方法模式·eit
霁月风21 小时前
设计模式——工厂方法模式
c++·设计模式·工厂方法模式
发飙的蜗牛'1 天前
23种设计模式
android·java·设计模式
NorthCastle1 天前
设计模式-创建型模式-简单工厂模式详解
设计模式·简单工厂模式