[设计模式/Java] 设计模式之门面模式(外观模式)【20】

概述 : 门面模式 := 外观模式 := Facade Pattern

产生背景

  • 软件开发过程中,我们经常会遇到复杂系统,其中包含多个子系统和接口。在这种情况下,为了简化客户端的调用过程提高代码的可维护性和可读性,我们可以使用门面模式。

模式定义

  • 门面模式Facade Pattern)也叫做外观模式,是一种结构型设计模式

它提供一个统一的接口,封装了一个或多个子系统的复杂功能,并向客户端提供一个简单的调用方式
通过引入门面客户端无需直接与子系统交互,而只需要通过门面来与子系统进行通信。

模式的组成

  • 门面Facade):门面角色是门面模式的核心,它封装了系统内部复杂子系统的接口,为客户端提供一个简单的高层接口。门面角色知道哪些子系统负责处理请求,并将请求转发给相应的子系统进行处理。
  • 子系统Subsystem):子系统角色是实际执行系统功能的组件。每个子系统都有自己的职责和行为,通过门面角色对外提供服务。
  • 客户端Client):客户端角色通过调用门面角色提供的高层接口来使用系统功能,而无需直接与子系统交互。

门面模式中,门面角色充当了客户端子系统之间的中介者隐藏了子系统的复杂性简化了客户端的调用过程
客户端只需要与门面角色进行交互,而不需要了解和处理子系统的具体细节

  • 【特别注意】
  • 门面对象只是提供一个访问子系统的一个路径而已,它不应该也不能参与具体的业务逻辑;
  • 否则,就会产生一个倒依赖的问题:子系统必须依赖门面才能被访问,这是设计上一个严重错误,不仅会违反了单一职责原则,同时也破坏了系统的封装性

适用场景

  • 当一个系统有很多复杂的子系统时,可以使用门面模式将其封装起来,隐藏内部复杂性,简化客户端的调用。
  • 当需要将客户端复杂的子系统解耦,降低系统之间的依赖时,可以使用门面模式

模式特点

优点

  • 简化客户端的调用过程,隐藏了子系统的复杂性,提供了一个统一的接口,客户端无需了解子系统的具体实现。
  • 减少系统的相互依赖,解耦了客户端与子系统之间的依赖关系。
  • 提高了代码的可维护性和可读性

缺点

  • 门面模式可能会导致门面类变得庞大承担过多的责任
  • 如果需要修改子系统的功能,可能需要修改门面类

门面模式的优化

在实际应用中,我们可以对门面模式进行一些优化和扩展。以下是几个常见的优化实现方式:

子系统解耦

  • 门面类可以通过委托调用子系统的功能,而不是直接依赖具体的子系统

这样可以使得子系统能够独立演化,不受门面类的影响。

// 门面类
class Facade {
    private SubSystemInterface subSystemA;
    private SubSystemInterface subSystemB;

    public Facade() {
        subSystemA = new ConcreteSubSystemA();
        subSystemB = new ConcreteSubSystemB();
    }

    // 提供给客户端的接口
    public void operation() {
        subSystemA.operation();
        subSystemB.operation();
    }
}

// 子系统接口
interface SubSystemInterface {
    void operation();
}

// 具体的子系统A
class ConcreteSubSystemA implements SubSystemInterface {
    public void operation() {
        // 实现具体的功能
    }
}

// 具体的子系统B
class ConcreteSubSystemB implements SubSystemInterface {
    public void operation() {
        // 实现具体的功能
    }
}

多个门面类

  • 门面已经庞大到不能忍受的程度承担过多的责任时,可以考虑使用多个门面类
  • 每个门面类负责与特定的子系统交互,原则上建议按照功能拆分

比如,一个数据库操作的门面可以拆分为查询门面、删除门面、更新门面等。

// 子系统A的门面类
class SubSystemAFacade {
    private SubSystemA subSystemA;

    public SubSystemAFacade() {
        subSystemA = new SubSystemA();
    }

    // 提供给客户端的接口
    public void operation() {
        subSystemA.operationA();
    }
}

// 子系统B的门面类
class SubSystemBFacade {
    private SubSystemB subSystemB;

    public SubSystemBFacade() {
        subSystemB = new SubSystemB();
    }

    // 提供给客户端的接口
    public void operation() {
        subSystemB.operationB();
    }
}

通过上述优化实现方式,我们能够灵活地应对不同的需求和场景,提高了系统的可扩展性和维护性。

门面嵌套

  • 假设我们有一个文件处理系统,其中包括3个子系统:

文件读取(FileReader)、文件写入(FileWriter)和文件压缩(FileCompressor)。

  • 现在有2个模块来访问该子系统:
  • 通用模块(GeneralModule)可以完整地访问所有业务逻辑,而受限模块(RestrictedModule)只能访问文件读取操作。
  • 在这种情况下,我们可以在门面外再嵌套门面来解决接口权限问题,以供不同的模块访问。
// 子系统:文件读取
class FileReader {
    public void read(String filePath) {
        System.out.println("读取文件:" + filePath);
        // 具体的读取逻辑...
    }
}

// 子系统:文件写入
class FileWriter {
    public void write(String filePath, String content) {
        System.out.println("写入文件:" + filePath);
        // 具体的写入逻辑...
    }
}

// 子系统:文件压缩
class FileCompressor {
    public void compress(String filePath, String destinationPath) {
        System.out.println("压缩文件:" + filePath + " -> " + destinationPath);
        // 具体的压缩逻辑...
    }
}

// 通用模块门面
class GeneralFacade {
    private FileReader fileReader;
    private FileWriter fileWriter;
    private FileCompressor fileCompressor;

    public GeneralFacade() {
        this.fileReader = new FileReader();
        this.fileWriter = new FileWriter();
        this.fileCompressor = new FileCompressor();
    }

    public void processFile(String filePath, String content, String destinationPath) {
        fileReader.read(filePath);
        fileWriter.write(filePath, content);
        fileCompressor.compress(filePath, destinationPath);
    }
    
    public void read(String filePath) {
        fileReader.read(filePath);
    }
    
}

// 受限模块门面
class RestrictedFacade {
    private GeneralFacade generalFacade = new GeneralFacade();
    
    public void readRestrictedFile(String filePath) {
        generalFacade.read(filePath);
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        GeneralFacade generalFacade = new GeneralFacade();
        generalFacade.processFile("file.txt", "Hello World!", "compressed.zip");

        RestrictedFacade restrictedFacade = new RestrictedFacade();
        restrictedFacade.readRestrictedFile("file.txt");
    }
}
  • 在上述示例中,我们使用了2个不同的门面GeneralFacadeRestrictedFacade
  • GeneralFacade提供了完整的访问子系统的方法processFile
  • RestrictedFacade仅提供了受限的文件读取方法readRestrictedFile

通过不同的门面对象通用模块可以访问所有子系统功能,而受限模块只能访问特定的子系统功能。

案例实践

CASE 门面模式的简单实现

SubSystemA / SubSystemB

  • 子系统A
// 子系统A
public class SubSystemA {
    public void operationA() {
        System.out.println("子系统A的操作");
    }
}
  • 子系统B
public class SubSystemB {
    public void operationB() {
        System.out.println("子系统B的操作");
    }
}
  • 子系统C
public class SubSystemC {
    public void operationC() {
        System.out.println("子系统C的操作");
    }
}

Facade/门面类

public class Facade {
    private SubSystemA subSystemA;
    private SubSystemB subSystemB;
    private SubSystemC subSystemC;

    public Facade() {
        subSystemA = new SubSystemA();
        subSystemB = new SubSystemB();
        subSystemC = new SubSystemC();
    }

    // 提供简单的接口给客户端调用,隐藏了子系统的复杂性
    public void operation() {
        subSystemA.operationA();
        subSystemB.operationB();
        subSystemC.operationC();
    }
}

CASE 电商系统

  • 场景描述:

假设我们的电子商务系统包含了订单管理库存管理支付管理等子系统。
为了简化客户端的调用过程,我们可以使用门面模式来封装这些子系统,并提供一个统一的接口。

OrderService/订单管理子系统

// 订单管理子系统
class OrderService {
    public void createOrder() {
        // 创建订单的具体实现
    }
}

InventoryService/库存管理子系统

// 库存管理子系统
class InventoryService {
    public void checkStock() {
        // 检查库存的具体实现
    }
}

PaymentService/支付管理子系统

// 支付管理子系统
class PaymentService {
    public void makePayment() {
        // 支付的具体实现
    }
}

ECommerceFacade/电子商务门面类

// 电子商务门面类
class ECommerceFacade {
    private OrderService orderService;
    private InventoryService inventoryService;
    private PaymentService paymentService;

    public ECommerceFacade() {
        orderService = new OrderService();
        inventoryService = new InventoryService();
        paymentService = new PaymentService();
    }

    // 提供给客户端的接口
    public void placeOrder() {
        orderService.createOrder();
        inventoryService.checkStock();
        paymentService.makePayment();
    }
}
  • 我们创建了一个电子商务门面类(ECommerceFacade),它封装了订单管理、库存管理和支付管理等子系统,并提供了一个简单的接口placeOrder)供客户端调用。

这样,客户端只需要通过门面类来完成下单操作,而无需直接与子系统交互。

CASE Shape(形状接口) 与 ShapeMaker(形状创建器外观类)

场景描述

  • 我们将创建一个 Shape 接口和实现了 Shape 接口的实体类。下一步是定义一个外观类 ShapeMaker
  • ShapeMaker 类使用实体类来代表用户对这些类的调用。
  • FacadePatternDemo 类使用 ShapeMaker 类来显示结果。

Shape :抽象接口

public interface Shape {
   void draw();
}

Rectangle / Square :具体的接口实现类(子系统)

  • Rectangle
public class Rectangle implements Shape {
   @Override
   public void draw() {
      System.out.println("Rectangle::draw()");
   }
}
  • Square
public class Square implements Shape {
   @Override
   public void draw() {
      System.out.println("Square::draw()");
   }
}
  • Circle
public class Circle implements Shape {
   @Override
   public void draw() {
      System.out.println("Circle::draw()");
   }
}

ShapeMaker(外观类)

public class ShapeMaker {
   private Shape circle;
   private Shape rectangle;
   private Shape square;
 
   public ShapeMaker() {
      circle = new Circle();
      rectangle = new Rectangle();
      square = new Square();
   }
 
   public void drawCircle(){
      circle.draw();
   }
   public void drawRectangle(){
      rectangle.draw();
   }
   public void drawSquare(){
      square.draw();
   }
}

FacadePatternDemo

  • 使用该外观类画出各种类型的形状。
public class FacadePatternDemo {
   public static void main(String[] args) {
      ShapeMaker shapeMaker = new ShapeMaker();
 
      shapeMaker.drawCircle();
      shapeMaker.drawRectangle();
      shapeMaker.drawSquare();      
   }
}

out

Circle::draw()
Rectangle::draw()
Square::draw()

Y 推荐文献

X 参考文献

From:https://www.cnblogs.com/johnnyzen/p/18840230
千千寰宇
100+评论
captcha