0%

外观模式

外观模式

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

问题

假设你必须在代码中使用某个复杂的库或框架中的众多对象。 正常情况下, 你需要负责所有对象的初始化工作、 管理其依赖关系并按正确的顺序执行方法等。

最终, 程序中类的业务逻辑将与第三方类的实现细节紧密耦合, 使得理解和维护代码的工作很难进行。

解决方案

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

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

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

外观模式结构

structure-indexed-2x

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

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

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

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

  5. 客户端 (Client) 使用外观代替对子系统对象的直接调用。

简单例子

复杂视频转换库的例子.在本例中, 外观简化了复杂视频转换框架所进行的沟通工作。外观提供了仅包含一个方法的类, 可用于处理对框架中所需类的配置与以正确格式获取结果的复杂工作。

定义视频文件类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
VideoFile
public class VideoFile {
private String name;
private String codecType;

public VideoFile(String name) {
this.name = name;
this.codecType = name.substring(name.indexOf(".") + 1);
}

public String getCodecType() {
return codecType;
}

public String getName() {
return name;
}
}

定义视频转换接口,类以及工厂创建类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Codec
public interface Codec {
}
// MPEG4CompressionCodec
public class MPEG4CompressionCodec implements Codec {
public String type = "mp4";

}
// OggCompressionCodec
public class OggCompressionCodec implements Codec {
public String type = "ogg";
}

// CodecFactory
public class CodecFactory {
public static Codec extract(VideoFile file) {
String type = file.getCodecType();
if (type.equals("mp4")) {
System.out.println("CodecFactory: extracting mpeg audio...");
return new MPEG4CompressionCodec();
}
else {
System.out.println("CodecFactory: extracting ogg audio...");
return new OggCompressionCodec();
}
}
}

定义音频文件读取类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// BitrateReader
public class BitrateReader {
public static VideoFile read(VideoFile file, Codec codec) {
System.out.println("BitrateReader: reading file...");
return file;
}

public static VideoFile convert(VideoFile buffer, Codec codec) {
System.out.println("BitrateReader: writing file...");
return buffer;
}
}
// AudioMixer
public class AudioMixer {
public File fix(VideoFile result){
System.out.println("AudioMixer: fixing audio...");
return new File("tmp");
}
}

定义视频文件转换的门面类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}
}

客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 客户端代码
package refactoring_guru.facade.example;

import refactoring_guru.facade.example.facade.VideoConversionFacade;

import java.io.File;

public class Demo {
public static void main(String[] args) {
VideoConversionFacade converter = new VideoConversionFacade();
File mp4Video = converter.convertVideo("youtubevideo.ogg", "mp4");
// ...
}
}

总结

适合应用场景

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

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

  2. 如果需要将子系统组织为多层结构, 可以使用外观。

    创建外观来定义子系统中各层次的入口。 你可以要求子系统仅使用外观来进行交互, 以减少子系统之间的耦合。
    让我们回到视频转换框架的例子。 该框架可以拆分为两个层次: 音频相关和视频相关。 你可以为每个层次创建一个外观, 然后要求各层的类必须通过这些外观进行交互。 这种方式看上去与中介者模式非常相似。

实现方式

  1. 考虑能否在现有子系统的基础上提供一个更简单的接口。 如果该接口能让客户端代码独立于众多子系统类, 那么你的方向就是正确的。

  2. 在一个新的外观类中声明并实现该接口。 外观应将客户端代码的调用重定向到子系统中的相应对象处。 如果客户端代码没有对子系统进行初始化, 也没有对其后续生命周期进行管理, 那么外观必须完成此类工作。

  3. 如果要充分发挥这一模式的优势, 你必须确保所有客户端代码仅通过外观来与子系统进行交互。 此后客户端代码将不会受到任何由子系统代码修改而造成的影响, 比如子系统升级后, 你只需修改外观中的代码即可。

  4. 如果外观变得过于臃肿, 你可以考虑将其部分行为抽取为一个新的专用外观类。

优缺点

优点: 简化客户端与复杂子系统的交互,并且可以让客户端代码减少依赖于子系统。
缺点:外挂坑你成为与程序中所有类都耦合的上帝对象

与其它模式的区别

  1. 与适配器的区别:此模式定义了了一个新的接口,然后使用子系统中的多个对象。适配器试图使用已有的接口,并对原来的对象进行转换以适应客户端的要求,而且之作用于一个对象。

参考

  1. 外观模式