设计模式:门面模式

意图

门面模式(Facade)是一种结构型设计模式, 能为程序库、 框架或其他复杂类提供一个简单的接口。

解决方案

门面类为包含许多活动部件的复杂子系统提供一个简单的接口。 与直接调用子系统相比, 门面提供的功能可能比较有限, 但它却包含了客户端真正关心的功能。

如果你的程序需要与包含几十种功能的复杂库整合, 但只需使用其中非常少的功能, 那么使用门面模式会非常方便,

例如, 上传猫咪搞笑短视频到社交媒体网站的应用可能会用到专业的视频转换库, 但它只需使用一个包含 encode­(filename, format)方法 (以文件名与文件格式为参数进行编码的方法) 的类即可。 在创建这个类并将其连接到视频转换库后, 你就拥有了自己的第一个门面。

门面模式结构

外观设计模式的结构

门面 (Facade) 提供了一种访问特定子系统功能的便捷方式, 其了解如何重定向客户端请求, 知晓如何操作一切活动部件。

创建附加门面 (Additional Facade) 类可以避免多种不相关的功能污染单一门面, 使其变成又一个复杂结构。 客户端和其他门面都可使用附加门面。

复杂子系统 (Complex Subsystem) 由数十个不同对象构成。 如果要用这些对象完成有意义的工作, 你必须深入了解子系统的实现细节, 比如按照正确顺序初始化对象和为其提供正确格式的数据。

子系统类不会意识到门面的存在, 它们在系统内运作并且相互之间可直接进行交互。

客户端 (Client) 使用门面代替对子系统对象的直接调用。

门面模式适合应用场景

  • 如果你需要一个指向复杂子系统的直接接口, 且该接口的功能有限, 则可以使用门面模式。

子系统通常会随着时间的推进变得越来越复杂。 即便是应用了设计模式, 通常你也会创建更多的类。 尽管在多种情形中子系统可能是更灵活或易于复用的, 但其所需的配置和样板代码数量将会增长得更快。 为了解决这个问题, 门面将会提供指向子系统中最常用功能的快捷方式, 能够满足客户端的大部分需求。

  • 如果需要将子系统组织为多层结构, 可以使用门面。

创建门面来定义子系统中各层次的入口。 你可以要求子系统仅使用门面来进行交互, 以减少子系统之间的耦合。

门面模式优缺点

优点:

  • 你可以让自己的代码独立于复杂子系统。

缺点:

  • 门面可能成为与程序中所有类都耦合的上帝对象。

与其他模式的关系

  • 门面模式为现有对象定义了一个新接口, 适配器模式则会试图运用已有的接口。 适配器通常只封装一个对象, 门面通常会作用于整个对象子系统上。

  • 当只需对客户端代码隐藏子系统创建对象的方式时, 你可以使用抽象工厂模式来代替门面。

  • 享元模式展示了如何生成大量的小型对象, 门面则展示了如何用一个对象来代表整个子系统。

  • 门面和中介者模式的职责类似: 它们都尝试在大量紧密耦合的类中组织起合作。

    • 门面为子系统中的所有对象定义了一个简单接口, 但是它不提供任何新功能。 子系统本身不会意识到门面的存在。 子系统中的对象可以直接进行交流。
    • 中介者将系统中组件的沟通行为中心化。 各组件只知道中介者对象, 无法直接相互交流。
  • 门面类通常可以转换为单例模式类, 因为在大部分情况下一个门面对象就足够了。

  • 门面与代理模式的相似之处在于它们都缓存了一个复杂实体并自行对其进行初始化。 代理与其服务对象遵循同一接口, 使得自己和服务对象可以互换, 在这一点上它与门面不同。

在 Java 中使用模式

使用示例: 使用 Java 开发的程序中经常会使用门面模式。 它在与复杂程序库和 API 协作时特别有用。

下面是一些核心 Java 程序库中的门面示例:

示例代码

门面 (Facade)

public class VideoConversionFacade {
  public File convertVideo(String fileName, String format) {
    System.out.println("VideoConversionFacade: conversion started.");
    VideoFile file = new VideoFile(fileName);
    Codec sourceCodec = CodecFactory.extract(file);
    Codec destinationCodec;
    if (format.equals("mp4")) {
      destinationCodec = new OggCompressionCodec();
    } else {
      destinationCodec = new MPEG4CompressionCodec();
    }
    VideoFile buffer = BitrateReader.read(file, sourceCodec);
    VideoFile intermediateResult = BitrateReader.convert(buffer, destinationCodec);
    File result = (new AudioMixer()).fix(intermediateResult);
    System.out.println("VideoConversionFacade: conversion completed.");
    return result;
  }
}

客户端 (Client)

public class Client {
  public static void main(String[] args) {
    VideoConversionFacade converter = new VideoConversionFacade();
    File mp4Video = converter.convertVideo("youtubevideo.ogg", "mp4");
    System.out.println(mp4Video.getName());
  }
}