23种设计模式漫画版系列—外观设计模式

简介: 23种设计模式漫画版系列—外观设计模式

意图


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


02问题


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

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


03解决方案


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

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

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


04真实世界类比

电话购物


当你通过电话给商店下达订单时 接线员就是该商店的所有服务和部门的外观 接线员为你提供了一个同购物系统 支付网关和各种送货服务进行互动的简单语音接口


05外观模式结构



06伪代码


在本例中外观模式简化了客户端与复杂视频转换框架之间的交互

使用单个外观类隔离多重依赖的示例


你可以创建一个封装所需功能并隐藏其他代码的外观类 从而无需使全部代码直接与数十个框架类进行交互 该结构还能将未来框架升级或更换所造成的影响最小化 因为你只需修改程序中外观方法的实现即可

// 这里有复杂第三方视频转换框架中的一些类。我们不知晓其中的代码,因此无法
// 对其进行简化。
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()

07外观模式适合应用场景

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

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

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

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

让我们回到视频转换框架的例子 该框架可以拆分为两个层次 音频相关和视频相关 你可以为每个层次创建一个外观 然后要求各层的类必须通过这些外观进行交互 这种方式看上去与中介者模式非常相似

08实现方式


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

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

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

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

09外观模式优缺点


  • 你可以让自己的代码独立于复杂子系统
  • 外观可能成为与程序中所有类都耦合的上帝对象

10与其他模式的关系


  • 外观模式为现有对象定义了一个新接口 适配器模式则会试图运用已有的接口适配器通常只封装一个对象外观通常会作用于整个对象子系统上
  • 当只需对客户端代码隐藏子系统创建对象的方式时 你可以使用抽象工厂模式来代替外观
  • 享元模式展示了如何生成大量的小型对象 外观则展示了如何用一个对象来代表整个子系统
  • 外观和中介者模式的职责类似 它们都尝试在大量紧密耦合的类中组织起合作
  • 外观为子系统中的所有对象定义了一个简单接口 但是它不提供任何新功能 子系统本身不会意识到外观的存在 子系统中的对象可以直接进行交流
  • 中介者将系统中组件的沟通行为中心化 各组件只知道中介者对象 无法直接相互交流
  • 外观类通常可以转换为单例模式 因为在大部分情况下一个外观对象就足够了
  • 外观与代理模式的相似之处在于它们都缓存了一个复杂实体并自行对其进行初始化代理与其服务对象遵循同一接口 使得自己和服务对象可以互换 在这一点上它与外观不同

11推荐UML使用工具


亿图图示

关注公众号:全栈芬达,回复亿图图示,获取激活版。

初学者秒会的专业级UML图绘制软件。无需掌握复杂操作,可以零基础轻松绘制280+种绘图类型


Visio


12UML  类图关系

UML类图非常简单,可以用下面的图表示一个类:


该图表示一个叫做Person的类,该类有name、age、sex三个private属性,每个属性的类型紧跟在冒号的后面。该类有walk和speak两个方法,其中walk方法是public的,而speak方法是protected的,两个方法的返回值类型紧跟在冒号的后面。

+:公有属性,其它类可以访问该属性

-:私有属性,不能被其它类访问(默认为私有)

#:保护属性,只能被本类及其派生类访问

~:包内可见,可以被本包中的其它类访问

如果要表示一个接口,则用下面的图表示:

下面介绍类与类之间的关系。如果按照关系的紧密程度从弱到强划分,类与类之间的关系包括:

  • 依赖
  • 关联
  • 聚合
  • 组合
  • 实现
  • 继承


依赖关系

依赖关系是所有类间关系中最弱的一种,它用下面的图表示:


图中的箭头方向表示依赖的方向,上图表示类A依赖类B。

依赖,顾名思义表示一个实体的存在必须依赖另一个实体的存在。可以这样认为,如果类A依赖类B,那么类A只有在类B存在的情况下,才能编译通过。

下面代码是依赖的一个例子:

public class UserController {

private UserService userService;


public User query(Strint userId) {

User user = userService.queryUser(userId);


return user;


}


}

在这段代码,UserController类同时依赖于UserService和User两个类,可以用下面的类图表示它们的依赖关系:


可见依赖关系大量的存在于我们的代码中,但千万不要在项目设计时将全部的依赖关系都画出来,这不仅很累,而且也没有必要。当梳理依赖关系时,先要搞清楚你关注什么,想表达什么,只画出真正需要画的就可以。


关联关系

关联关系表示两个实体间存在一定的联系,这种联系比依赖关系更紧密,不仅仅只是“两个实体触碰到”这样松散的关系。例如Student和School这两个类,一个学生一定会有一个对应的学校,那么Student和School间就存在关联关系,且它们的关系是一对多的。


用下面的UML图表示:


关联关系也可以用于领域建模,例如要设计一个骰子游戏,游戏者连续投掷两次筛子,如果两次点数的总数是7,则游戏者赢,否则游戏者输。可以用下面UML图对这个问题进行领域建模,各实体间使用的就是关联关系。这也是关联关系的一种特殊用法。

聚合&组合

聚合也是一种关联关系,但是这种关联关系存在整体与部分的语义。例如大雁和大雁群,一只大雁是整个大雁群的一部分。这就是一种聚合关系,具有has-a的语义。下面的UML图用来描述聚合关系。

组合是一种强聚合关系,它表示整体和部分之间具有相同的生命周期,同生共死。例如鸟和翅膀,鸟如果死掉了,那么它的翅膀也会跟着死掉。组合关系具有contains-a的语义。下面的UML图用于表达组合关系。


记忆聚合和组合UML图画法的小技巧:菱形就相当于一个容器,容器指向的实体就是整体,所以上面图中的菱形分别指向大雁群和鸟。此外,由于组合关系的紧密程度比聚合关系更强,所以组合关系用实心菱形,聚合关系用空心菱形。


继承&实现

继承和实现都是Java中的基础,比较容易理解,它们是类与类之间关系最强的。分别用下面的UML图表示。

继承示例:


实现示例:


PS:实现关系应该用空心箭头。

相关文章
|
7天前
|
设计模式 API
【设计模式】适配器和桥接器模式有什么区别
【设计模式】适配器和桥接器模式有什么区别
10 1
|
7天前
|
设计模式
【设计模式】张一鸣笔记:责任链接模式怎么用?
【设计模式】张一鸣笔记:责任链接模式怎么用?
11 1
|
7天前
|
设计模式 uml
【设计模式】建造者模式就是游戏模式吗?
【设计模式】建造者模式就是游戏模式吗?
11 0
|
7天前
|
设计模式 Java uml
【设计模式】什么是工厂方法模式?
【设计模式】什么是工厂方法模式?
8 1
|
7天前
|
设计模式 uml
【设计模式】一文搞定简单工厂模式!
【设计模式】一文搞定简单工厂模式!
8 2
|
7天前
|
设计模式 JavaScript 前端开发
js设计模式-观察者模式与发布/订阅模式
观察者模式和发布/订阅模式是JavaScript中的两种设计模式,用于处理对象间的通信和事件处理。观察者模式中,一个主题对象状态改变会通知所有观察者。实现包括定义主题和观察者对象,以及在主题中添加、删除和通知观察者的功能。发布/订阅模式则引入事件管理器,允许发布者发布事件,订阅者通过订阅接收通知。
|
7天前
|
设计模式 前端开发 Java
19:Web开发模式与MVC设计模式-Java Web
19:Web开发模式与MVC设计模式-Java Web
24 4
|
7天前
|
设计模式 消息中间件 Java
Java 设计模式:探索发布-订阅模式的原理与应用
【4月更文挑战第27天】发布-订阅模式是一种消息传递范式,被广泛用于构建松散耦合的系统。在 Java 中,这种模式允许多个对象监听和响应感兴趣的事件。
39 2
|
7天前
|
设计模式 存储 JavaScript
[设计模式Java实现附plantuml源码~创建型] 多态工厂的实现——工厂方法模式
[设计模式Java实现附plantuml源码~创建型] 多态工厂的实现——工厂方法模式
|
7天前
|
设计模式 Java Go
[设计模式Java实现附plantuml源码~创建型] 集中式工厂的实现~简单工厂模式
[设计模式Java实现附plantuml源码~创建型] 集中式工厂的实现~简单工厂模式