--- title: 设计模式之外观模式 date: 2015-01-19 15:15:00 order: 11 categories: - 设计 - 设计模式 tags: - 设计 - 设计模式 permalink: /pages/9a395a72/ --- # 设计模式之外观模式 ## 意图 **外观模式** (Facade) 是一种结构型设计模式, 为子系统中的**一组接口**提供一个一致的界面,此模式定义了**一个高层接口**,这个接口使得这一子系统更加容易使用。 - 外观模式为复杂子系统提供了一个简单接口,并不为子系统添加新的功能和行为。 - 外观模式实现了子系统与客户之间的松耦合关系。 - 外观模式没有封装子系统的类,只是提供了简单的接口。 如果应用需要,它并不限制客户使用子系统类。因此可以再系统易用性与通用性之间选择。 - 外观模式注重的是简化接口,它更多的时候是从架构的层次去看整个系统,而并非单个类的层次。 ## 适用场景 - 如果你需要一个指向复杂子系统的直接接口, 且该接口的功能有限, 则可以使用外观模式。 - 如果需要将子系统组织为多层结构, 可以使用外观。 ## 结构 ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210430174751.png) ### 结构说明 1. **外观** (Facade) 提供了一种访问特定子系统功能的便捷方式, 其了解如何重定向客户端请求, 知晓如何操作一切活动部件。 2. 创建**附加外观** (Additional Facade) 类可以避免多种不相关的功能污染单一外观, 使其变成又一个复杂结构。 客户端和其他外观都可使用附加外观。 3. **复杂子系统** (Complex Subsystem) 由数十个不同对象构成。 如果要用这些对象完成有意义的工作, 你必须深入了解子系统的实现细节, 比如按照正确顺序初始化对象和为其提供正确格式的数据。 子系统类不会意识到外观的存在, 它们在系统内运作并且相互之间可直接进行交互。 4. **客户端** (Client) 使用外观代替对子系统对象的直接调用。 ### 结构代码范式 **Facade** : 了解每个子系统类的功能,负责分发客户端的请求给各个子系统去处理。 ```java class Class1 { public void op1() { System.out.println("方法1"); } } class Class2 { public void op2() { System.out.println("方法2"); } } class Class3 { public void op3() { System.out.println("方法3"); } } ``` **Subsystem Classes** : 实现子系统功能。在不感知 Facade 的情况下,处理 Facade 对象分配的工作, ```java class Facade { private Class1 one = new Class1(); private Class2 two = new Class2(); private Class3 three = new Class3(); public void op1() { System.out.println("Facade op1()"); one.op1(); } public void op2() { System.out.println("Facade op2()"); two.op2(); } public void op3() { System.out.println("Facade op3()"); three.op3(); } public void Method() { System.out.println("Facade Method()"); three.op3(); two.op2(); one.op1(); } } ``` 【客户端】 ```java public class FacadePattern { public static void main(String[] args) { Facade facade = new Facade(); facade.Method(); facade.op1(); } } ``` 【输出】 ``` Facade Method() 方法3 方法2 方法1 Facade op1() 方法1 ``` ## 伪代码 在本例中, **外观**模式简化了客户端与复杂视频转换框架之间的交互。 ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210430175224.png) 你可以创建一个封装所需功能并隐藏其他代码的外观类, 从而无需使全部代码直接与数十个框架类进行交互。 该结构还能将未来框架升级或更换所造成的影响最小化, 因为你只需修改程序中外观方法的实现即可。 ```java // 这里有复杂第三方视频转换框架中的一些类。我们不知晓其中的代码,因此无法 // 对其进行简化。 class VideoFile // ... class OggCompressionCodec // ... class MPEG4CompressionCodec // ... class CodecFactory // ... class BitrateReader // ... class AudioMixer // ... // 为了将框架的复杂性隐藏在一个简单接口背后,我们创建了一个外观类。它是在 // 功能性和简洁性之间做出的权衡。 class VideoConverter is method convert(filename, format):File is file = new VideoFile(filename) sourceCodec = new CodecFactory.extract(file) if (format == "mp4") destinationCodec = new MPEG4CompressionCodec() else destinationCodec = new OggCompressionCodec() buffer = BitrateReader.read(filename, sourceCodec) result = BitrateReader.convert(buffer, destinationCodec) result = (new AudioMixer()).fix(result) return new File(result) // 应用程序的类并不依赖于复杂框架中成千上万的类。同样,如果你决定更换框架, // 那只需重写外观类即可。 class Application is method main() is convertor = new VideoConverter() mp4 = convertor.convert("funny-cats-video.ogg", "mp4") mp4.save() ``` ## 案例 **使用示例:** 使用 Java 开发的程序中经常会使用外观模式。 它在与复杂程序库和 API 协作时特别有用。 下面是一些核心 Java 程序库中的外观示例: - [`javax.faces.context.FacesContext`](http://docs.oracle.com/javaee/7/api/javax/faces/context/FacesContext.html) 在底层使用了 [`Life­Cycle`](http://docs.oracle.com/javaee/7/api/javax/faces/lifecycle/Lifecycle.html)、 [`View­Handler`](http://docs.oracle.com/javaee/7/api/javax/faces/application/ViewHandler.html) 和 [`Navigation­Handler`](http://docs.oracle.com/javaee/7/api/javax/faces/application/NavigationHandler.html) 这几个类, 但绝大多数客户端不知道。 - [`javax.faces.context.ExternalContext`](http://docs.oracle.com/javaee/7/api/javax/faces/context/ExternalContext.html) 在内部使用了 [`Servlet­Context`](http://docs.oracle.com/javaee/7/api/javax/servlet/ServletContext.html)、 [`Http­Session`](http://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpSession.html)、 [`Http­Servlet­Request`](http://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequest.html)、 [`Http­Servlet­Response`](http://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletResponse.html) 和其他一些类。 **识别方法**: 外观可以通过使用简单接口, 但将绝大部分工作委派给其他类的类来识别。 通常情况下, 外观管理着其所使用的对象的完整生命周期。 ## 与其他模式的关系 - [外观模式](https://refactoringguru.cn/design-patterns/facade)为现有对象定义了一个新接口, [适配器模式](https://refactoringguru.cn/design-patterns/adapter)则会试图运用已有的接口。 *适配器*通常只封装一个对象, *外观*通常会作用于整个对象子系统上。 - 当只需对客户端代码隐藏子系统创建对象的方式时, 你可以使用[抽象工厂模式](https://refactoringguru.cn/design-patterns/abstract-factory)来代替[外观](https://refactoringguru.cn/design-patterns/facade)。 - [享元模式](https://refactoringguru.cn/design-patterns/flyweight)展示了如何生成大量的小型对象, [外观](https://refactoringguru.cn/design-patterns/facade)则展示了如何用一个对象来代表整个子系统。 - [外观](https://refactoringguru.cn/design-patterns/facade)和[中介者模式](https://refactoringguru.cn/design-patterns/mediator)的职责类似: 它们都尝试在大量紧密耦合的类中组织起合作。 - *外观*为子系统中的所有对象定义了一个简单接口, 但是它不提供任何新功能。 子系统本身不会意识到外观的存在。 子系统中的对象可以直接进行交流。 - *中介者*将系统中组件的沟通行为中心化。 各组件只知道中介者对象, 无法直接相互交流。 - [外观](https://refactoringguru.cn/design-patterns/facade)类通常可以转换为[单例模式](https://refactoringguru.cn/design-patterns/singleton)类, 因为在大部分情况下一个外观对象就足够了。 - [外观](https://refactoringguru.cn/design-patterns/facade)与[代理模式](https://refactoringguru.cn/design-patterns/proxy)的相似之处在于它们都缓存了一个复杂实体并自行对其进行初始化。 *代理*与其服务对象遵循同一接口, 使得自己和服务对象可以互换, 在这一点上它与*外观*不同。 ## 参考资料 - [《Head First 设计模式》](https://book.douban.com/subject/2243615/) - [《大话设计模式》](https://book.douban.com/subject/2334288/) - [设计模式教程](https://refactoringguru.cn/design-patterns/catalog)