设计模式
设计模式简介
看懂UML类图和时序图
UML统一建模语言
UML类图及类图之间的关系
类关系记忆技巧
如何正确使用设计模式
优秀设计的特征
面向对象设计原则
创建型设计模式
工厂模式
抽象工厂模式
简单工厂模式
静态工厂模式(Static Factory)
单例模式
建造者模式
原型模式
结构型设计模式
适配器模式
桥接模式
组合模式
装饰器模式
外观模式
享元模式
代理模式
过滤器模式
注册模式(Registry)
行为型设计模式
责任链模式
命令模式
解释器模式
中介者模式
备忘录模式
迭代器模式
观察者模式
状态模式
策略模式
模板模式
访问者模式
规格模式(Specification)
J2EE 设计模式
MVC 模式
业务代表模式
组合实体模式
数据访问对象模式(DAO模式)
前端控制器模式
拦截过滤器模式
空对象模式
服务定位器模式
传输对象模式
数据映射模式(Data Mapper)
依赖注入模式(Dependency Injection)
流接口模式(Fluent Interface)
其他模式
对象池模式(Pool)
委托模式
资源库模式(Repository)
实体属性值模式(EAV 模式)
反面模式
归纳设计模式
本文档使用 MrDoc 发布
-
+
首页
外观模式
> 外观模式是一种结构型设计模式, 能为程序库、 框架或其他复杂类提供一个简单的接口。 ![外观设计模式](/media/202203/2022-03-05_2156480.6406126637604241.png) 外观模式(Facade Pattern、门面模式、Facade)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。 这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。 ## 问题 假设你必须在代码中使用某个复杂的库或框架中的众多对象。 正常情况下, 你需要负责所有对象的初始化工作、 管理其依赖关系并按正确的顺序执行方法等。 最终, 程序中类的业务逻辑将与第三方类的实现细节紧密耦合, 使得理解和维护代码的工作很难进行。 ## 解决方案 外观类为包含许多活动部件的复杂子系统提供一个简单的接口。 与直接调用子系统相比, 外观提供的功能可能比较有限, 但它却包含了客户端真正关心的功能。 如果你的程序需要与包含几十种功能的复杂库整合, 但只需使用其中非常少的功能, 那么使用外观模式会非常方便, 例如, 上传猫咪搞笑短视频到社交媒体网站的应用可能会用到专业的视频转换库, 但它只需使用一个包含 encode(filename, format)方法 (以文件名与文件格式为参数进行编码的方法) 的类即可。 在创建这个类并将其连接到视频转换库后, 你就拥有了自己的第一个外观。 ## 目的 门面模式的最初目的并不是为了避免让你阅读复杂的 API 文档,这只是一个附带作用。其实它的本意是为了降低耦合性并且遵循 Demeter 定律。 一个门面旨在通过嵌入许多(但有时只有一个)接口来分离客户端和子系统。当然,也是为了降低复杂度。 - 门面不会禁止你访问子系统。 - 你可以(应该)有多个门面对应一个子系统。 这就是为什么一个好的门面里没有 new 的原因。如果每个方法都有多种创建,那并不是一个门面,而是一个构建器 [抽象的 | 静态的 | 简单的] 或是一个工厂 [方法] 。 最好的门面是没有 new 的,并且其构造函数带有接口类型提示的参数。 如果你需要创建新的实例,可以使用工厂作为变量。 ## 介绍 **意图:** 为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。 **主要解决:** 降低访问复杂系统的内部子系统时的复杂度,简化客户端之间的接口。 **何时使用:** 1. 客户端不需要知道系统内部的复杂联系,整个系统只需提供一个"接待员"即可。 2. 定义系统的入口。 **如何解决:** 客户端不与系统耦合,外观类与系统耦合。 **关键代码:** 在客户端和复杂系统之间再加一层,这一层将调用顺序、依赖关系等处理好。 **应用实例:** 1. 去医院看病,可能要去挂号、门诊、划价、取药,让患者或患者家属觉得很复杂,如果有提供接待人员,只让接待人员来处理,就很方便。 2. JAVA 的三层开发模式。 **优点:** 1. 减少系统相互依赖。 2. 提高灵活性。 3. 提高了安全性。 **缺点:** 不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。 **使用场景:** 1. 为复杂的模块或子系统提供外界访问的模块。 2. 子系统相对独立。 3. 预防低水平人员带来的风险。 **注意事项:** 在层次化结构中,可以使用外观模式定义系统中每一层的入口。 ## 结构 ![外观设计模式的结构](/media/202203/2022-03-05_1837430.545277393892749.png) - 外观 (Facade) 提供了一种访问特定子系统功能的便捷方式, 其了解如何重定向客户端请求, 知晓如何操作一切活动部件。 - 创建附加外观 (Additional Facade) 类可以**避免多种不相关的功能污染单一外观**, 使其变成又一个复杂结构。 客户端和其他外观都可使用附加外观。 - 复杂子系统 (Complex Subsystem) 由数十个不同对象构成。 如果要用这些对象完成有意义的工作, 你必须深入了解子系统的实现细节, 比如按照正确顺序初始化对象和为其提供正确格式的数据。 子系统类不会意识到外观的存在, 它们在系统内运作并且相互之间可直接进行交互。 - 客户端 (Client) 使用外观代替对子系统对象的直接调用。 ### 实例 我们将创建一个 *Shape* 接口和实现了 *Shape* 接口的实体类。下一步是定义一个外观类 *ShapeMaker* 。 *ShapeMaker* 类使用实体类来代表用户对这些类的调用。*FacadePatternDemo* 类使用 *ShapeMaker* 类来显示结果。 ![外观模式的 UML 图](https://www.runoob.com/wp-content/uploads/2014/08/20201015-facade.svg) ## 伪代码 在本例中, 外观模式简化了客户端与复杂视频转换框架之间的交互。 ![外观模式示例的结构](/media/202203/2022-03-05_1845050.7894299996201763.png) 你可以创建一个封装所需功能并隐藏其他代码的外观类, 从而无需使全部代码直接与数十个框架类进行交互。 该结构还能将未来框架升级或更换所造成的影响最小化, 因为你只需修改程序中外观方法的实现即可。 ``` // 这里有复杂第三方视频转换框架中的一些类。我们不知晓其中的代码,因此无法 // 对其进行简化。 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() ``` ## 应用场景 - 如果你需要一个指向复杂子系统的直接接口, 且该接口的功能有限, 则可以使用外观模式。 子系统通常会随着时间的推进变得越来越复杂。 即便是应用了设计模式, 通常你也会创建更多的类。 尽管在多种情形中子系统可能是更灵活或易于复用的, 但其所需的配置和样板代码数量将会增长得更快。 为了解决这个问题, 外观将会提供指向子系统中最常用功能的快捷方式, 能够满足客户端的大部分需求。 - 如果需要**将子系统组织为多层结构, 可以使用外观**。 创建外观来定义子系统中各层次的入口。 你可以要求子系统仅使用外观来进行交互, 以减少子系统之间的耦合。 让我们回到视频转换框架的例子。 该框架可以拆分为两个层次: 音频相关和视频相关。 你可以为每个层次创建一个外观, 然后要求各层的类必须通过这些外观进行交互。 这种方式看上去与中介者模式非常相似。 ## 实现方式 1. 考虑能否在现有子系统的基础上提供一个更简单的接口。 如果该接口能让客户端代码独立于众多子系统类, 那么你的方向就是正确的。 2. 在一个新的外观类中声明并实现该接口。 外观应将客户端代码的调用重定向到子系统中的相应对象处。 如果客户端代码没有对子系统进行初始化, 也没有对其后续生命周期进行管理, 那么外观必须完成此类工作。 3. 如果要充分发挥这一模式的优势, 你必须**确保所有客户端代码仅通过外观来与子系统进行交互**。 此后客户端代码将不会受到任何由子系统代码修改而造成的影响, 比如子系统升级后, 你只需修改外观中的代码即可。 4. 如果外观变得过于臃肿, 你可以考虑将其部分行为抽取为一个新的专用外观类。 ## 优点 - 你可以让自己的代码独立于复杂子系统。 ## 缺点 - **外观可能成为与程序中所有类都耦合的上帝对象**。 ## 与其他模式的关系 - 外观模式为现有对象定义了一个新接口, 适配器模式则会试图运用已有的接口。 适配器通常只封装一个对象, 外观通常会作用于整个对象子系统上。 - 当只需对客户端代码隐藏子系统创建对象的方式时, 你可以使用抽象工厂模式来代替外观。 - 享元模式展示了如何生成大量的小型对象, 外观则展示了如何用一个对象来代表整个子系统。 - 外观和中介者模式的职责类似: 它们都尝试在大量紧密耦合的类中组织起合作。 - 外观为子系统中的所有对象定义了一个简单接口, 但是它不提供任何新功能。 子系统本身不会意识到外观的存在。 子系统中的对象可以直接进行交流。 - 中介者将系统中组件的沟通行为中心化。 各组件只知道中介者对象, 无法直接相互交流。 - 外观类通常可以转换为单例模式类, 因为在大部分情况下一个外观对象就足够了。 - 外观与代理模式的相似之处在于它们都缓存了一个复杂实体并自行对其进行初始化。 代理与其服务对象遵循同一接口, 使得自己和服务对象可以互换, 在这一点上它与外观不同。 ## 示例代码 ### TS ``` /** * The Facade class provides a simple interface to the complex logic of one or * several subsystems. The Facade delegates the client requests to the * appropriate objects within the subsystem. The Facade is also responsible for * managing their lifecycle. All of this shields the client from the undesired * complexity of the subsystem. */ class Facade { protected subsystem1: Subsystem1; protected subsystem2: Subsystem2; /** * Depending on your application's needs, you can provide the Facade with * existing subsystem objects or force the Facade to create them on its own. */ constructor(subsystem1: Subsystem1 = null, subsystem2: Subsystem2 = null) { this.subsystem1 = subsystem1 || new Subsystem1(); this.subsystem2 = subsystem2 || new Subsystem2(); } /** * The Facade's methods are convenient shortcuts to the sophisticated * functionality of the subsystems. However, clients get only to a fraction * of a subsystem's capabilities. */ public operation(): string { let result = 'Facade initializes subsystems:\n'; result += this.subsystem1.operation1(); result += this.subsystem2.operation1(); result += 'Facade orders subsystems to perform the action:\n'; result += this.subsystem1.operationN(); result += this.subsystem2.operationZ(); return result; } } /** * The Subsystem can accept requests either from the facade or client directly. * In any case, to the Subsystem, the Facade is yet another client, and it's not * a part of the Subsystem. */ class Subsystem1 { public operation1(): string { return 'Subsystem1: Ready!\n'; } // ... public operationN(): string { return 'Subsystem1: Go!\n'; } } /** * Some facades can work with multiple subsystems at the same time. */ class Subsystem2 { public operation1(): string { return 'Subsystem2: Get ready!\n'; } // ... public operationZ(): string { return 'Subsystem2: Fire!'; } } /** * The client code works with complex subsystems through a simple interface * provided by the Facade. When a facade manages the lifecycle of the subsystem, * the client might not even know about the existence of the subsystem. This * approach lets you keep the complexity under control. */ function clientCode(facade: Facade) { // ... console.log(facade.operation()); // ... } /** * The client code may have some of the subsystem's objects already created. In * this case, it might be worthwhile to initialize the Facade with these objects * instead of letting the Facade create new instances. */ const subsystem1 = new Subsystem1(); const subsystem2 = new Subsystem2(); const facade = new Facade(subsystem1, subsystem2); clientCode(facade); ``` 输出: ``` Facade initializes subsystems: Subsystem1: Ready! Subsystem2: Get ready! Facade orders subsystems to perform the action: Subsystem1: Go! Subsystem2: Fire! ```
追风者
2022年3月24日 13:41
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
关于 MrDoc
觅思文档MrDoc
是
州的先生
开发并开源的在线文档系统,其适合作为个人和小型团队的云笔记、文档和知识库管理工具。
如果觅思文档给你或你的团队带来了帮助,欢迎对作者进行一些打赏捐助,这将有力支持作者持续投入精力更新和维护觅思文档,感谢你的捐助!
>>>捐助鸣谢列表
微信
支付宝
QQ
PayPal
Markdown文件
分享
链接
类型
密码
更新密码