行为型设计模式

行为型设计模式

创建型设计模式主要解决 “对象的创建” 问题,结构型设计模式主要解决 “类或对象的组合” 问题,那行为型设计模式主要解决的就是 “类与对象之间的交互” 问题。行为型模式比较多,有 11 种,它们分别是:观察者模式、模板模式、策略模式、职责链模式、迭代器模式、状态模式、访问者模式、备忘录模式、命令模式、解释器模式、中介模式。

1. 观察者模式

观察者模式将观察者和被观察者代码解耦。观察者模式的应用场景非常广泛,小到代码层面的解耦,大到结构层面的系统解耦,再或者一些产品的设计思路,都有这种模式的影子,比如邮件订阅、RSS Feeds,本质上都是观察者模式。

不同应用场景和需求下,该模式也有截然不同的实现方式:同步阻塞的实现方式、异步非阻塞的实现方式;进程内的实现方式、跨进程的实现方式。同步阻塞是最经典的实现模式,主要是为了代码解耦;异步非阻塞除了能实现代码解耦之外,还能提高代码的执行效率;进程间的观察者模式解耦更加彻底,一般是基于消息队列来实现,用来实现不同进程间的被观察者和观察者之间的交互。

2. 模板模式

定义:在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。这里的算法,可以理解为广义上的业务逻辑,并不特指数据结构和算法中的算法。这里的算法骨架就是模板,包含算法骨架的方法就是模板方法,这也是模板方法模式名字的由来。

作用:复用和扩展。复用是指所有子类可以复用父类中提供的模板方法的代码。扩展指的是,框架通过模板模式提供的功能扩展点,让框架用户在不修改源码的情况下,基于扩展点定制化框架的功能。

回调,跟模板模式具有相同的作用:代码复用和扩展。在一些框架、类库、组件等的设计中经常会用到,比如 jdbcTemplate 就是用了回调。

相对于普通的函数调用,回调是一种双向调用关系。A 类事先注册某个函数 F 到 B 类,A 类在调用 B 类的 P 函数的时候,B 类反过来调用 A 类注册给它的 F 函数。这里的 F 函数就是回调函数。A 调用 B ,B 反过来又调用 A,这种调用机制就叫作回调。

回调可以细分为同步回调和异步回调。从应用场景上来看,同步回调更像模板模式,异步回调更像观察者模式。回调与模板模式的区别,更多是代码实现上,而非应用场景上。回调基于组合关系来实现,模板模式基于继承关系来实现。回调比模板模式更加灵活。

3. 策略模式

定义:定义一组算法,将每个算法分别封装起来,让它们可以互相替换。策略模式将算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)。策略模式用来解耦策略的定义创建使用。实际上,一个完整的策略模式就是由这三个部分组成的。

策略类的定义比较简单,包含一个策略接口和一组实现这个接口的策略类。策略的创建由工厂类来完成,封装策略创建的细节。策略模式包含一组策略可选,客户端代码选择使用哪个策略,有两种确定方法:编译时静态确定和运行时动态确定。其中,运行时动态确定才是策略模式最典型的应用场景。

场景: 利用策略模式,可以规避冗长的 if-else 或 switch 分支判断。不过,作用不止如此。还可以像模板模式那样,提供框架的扩展点等等。实际上,策略模式主要作用还是解耦策略的定义、创建和使用,控制代码的复杂度,让每个部分不至于过于复杂、代码量过多。除此之外,对于复杂代码来说,策略模式还能让其满足开闭原则,添加新策略的时候,最小化,集中化代码改动,减少引入 bug 的风险。

4. 职责链模式

   在 GOF 定义中,一旦某个处理器能处理这个请求,就不会继续将请求传递给后续的处理器了。在实际的开发中,也存在对这个模式的变种,那就是请求不会中途终止传递,而是会被所有的处理器都处理一遍。
  • 定义:多个处理器依次处理同一个请求。一个请求先经过 A 处理器处理,然后再把请求传递给 B 处理器, B 处理器处理完再传递给 C 处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫作职责链模式。
  • 场景:常用在框架开发中,用来实现过滤器、拦截器功能,让框架的使用者在不需要修改框架源码的情况下,添加新的过滤、拦截功能。这也体现了之前讲到的对扩展开发,对修改关闭的设计原则。

5. 迭代器模式

   迭代器模式也称为游标模式,它用来遍历集合对象。这里说的集合对象,我们也可以叫作“容器”、“聚合对象”,实际上就是包含一组对象的对象,比如,数组、链表、树、图、跳表。迭代器模式主要作用就是解耦容器代码和遍历代码。大部分的编程语言都提供了现成的迭代器可以使用,我们不需要从零开始开发。

   遍历集合一般有三种方式:for 循环、foreach 循环、迭代器遍历。后两种本质上属于一种,都可以看作迭代器遍历。相对于 for 循环遍历,利用迭代器遍历有 3 个优势:
  • 迭代器模式封装集合内部复杂的数据结构,开发者不需要了解如何遍历,直接使用容器提供的迭代器即可。
  • 迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器中,让两者的职责更加单一。
  • 迭代器模式让添加新的遍历算法更加容易,更符合开闭原则。除此以外,因为迭代器都实现自相同的即可,在开发中基于接口而非实现编程,替换迭代器也变得更加容易。

在通过迭代器遍历集合元素的同时,增加或删除集合中的元素,有可能导致某个元素被重复遍历或者遍历不到。针对这个问题,有两种比较干脆利索的解决方案,来避免出现这种不可预期的运行结果。

  • 遍历的时候不允许增删元素
  • 增删元素之后让遍历报错

第一种解决方案比较难实现,因为很难确定迭代器使用结束的时间点。第二种解决方案更加合理,Java 语言采用的就是第二种解决方案。增删元素后,我们选择 fail-fast 解决方式,让遍历操作直接抛出运行时异常。

6. 状态模式

状态模式一般用来实现状态机,而状态机常用在游戏、工作引擎等系统开发中。状态机又叫有限状态机,它由 3 个部分组成:状态、事件、动作。其中,事件也称为转移条件。其中事件触发状态的转移和动作的执行。不过,动作不是必须的,也可能只转移状态,不执行任何动作。

针对状态机,有三种实现方式:

  • 分支逻辑法。利用 if-else 或 Switch-case 分支逻辑,参照状态转移图,将每一个状态转移原模原样的直译成代码。对于简单的状态机来说,这种实现方式最简单,最直接,是首选。
  • 查表法。对于状态很多、状态转移比较复杂的状态机来说,查表法比较适合。通过二维数组来表示状态转移图,能极大地提高代码的可读性和可维护性。
  • 状态模式。对于状态不多,状态转移也比较简单,但是事件触发执行的动作包含的业务逻辑可能比较复杂的状态机来说,这种方式是首选。

7. 访问者模式

访问者模式允许一个或者多个操作应用到一组对象上,设计意图是解耦操作和对象本身,保持类职责单一、满足开闭原则以及应对代码的复杂性。

对于访问者模式,主要的难点是在代码实现上。而代码实现比较复杂的主要原因是,函数重载在大部分面向对象编程语言中是静态绑定的。也就是说,调用类的哪个重载函数,在编译期间,由参数的声明类型决定的,而非运行时,根据参数的实际类型决定的。除此之外,我们还讲到Double Disptach。如果某种语言支持 Double Dispatch,那么就不需要访问者模式了。

正是因为代码实现难理解,所以,在项目中使用这种模式,会导致代码的可读性比较差。如果你的同事不了解这种设计模式,可能会读不懂,维护不了写的代码。所以,除非不得已,不要使用这种模式。

8. 备忘录模式

备忘录模式也被称为快照模式,具体来说,就是在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。这个模式的定义表达了两部分内容:一部分是:存储副本以便后期恢复;另一部分是,要在不违背封装原则的前提下,进行对象的备份和恢复。

备忘录模式的应用场景也比较明确和有限,主要用来防丢失、撤销、恢复等。它跟平时常说的“备份”很相似。两者的主要区别在于,备忘录模式更侧重于代码的设计和实现,备份更侧重架构设计或产品设计。

对于大对象的备份来说,备份占用的存储空间比较大,备份和恢复的耗时会比较长。针对这个问题,不同的业务场景会有不同的处理方式。比如,只备份必要的恢复信息,结合最新的数据来恢复;再比如,全量备份和增量备份相结合,低频全量备份,高频增量备份,两者结合来做恢复。

9. 命令模式

命令模式在平时工作中不常用,稍微了解一下就可以。

落实到编码实现,命令模式用到最核心的实现手段,就是将函数封装成对象。我们知道,在大部分编程语言中,函数是没办法作为参数传递给其他函数的,也没法赋值给变量。借助命令模式,我们将函数封装成对象,这样就可以实现把函数像对象一样使用。

命令模式的主要作用和应用场景,是用来控制命令的执行,比如,异步、延迟、排队执行命令、撤销重做命令、存储命令、给命令记录日志等,这才是命令模式能发挥独一无二作用的地方。

10. 解释器模式

解释器模式为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法。实际上这里的“语言”不仅仅指我们平时说的中、英、日、法等各种语言。从广义上来讲,只要是能承载信息的载体,我们都可以称之为语言,比如,古代的结绳记事、盲文、哑语、摩斯密码等。

要想了解“语言”要表达的信息,我们就必须定义相应的语法规则。这样,书写者就可以根据语法规则来书写“句子”(专业点的叫法应该是表达式),阅读者根据语法规则来阅读“句子”,这样才能做到信息的正确传递。而解释器模式,其实就是用来实现根据语法规则解读“句子”的解释器。

解释器模式的代码实现比较灵活,没有固定的模板。应用设计模式主要是应对代码的复杂性,解释器模式也不例外。它代码实现的核心思想,就是将语法解析的工作拆分到各个小类中,以此来避免大而全的解析器。一般的做法是,将语法规则拆分为一些小的独立的单元,然后对每个单元进行解析,最终合并为对整个语法规则的解析。

11. 中介模式

中介模式的设计思想跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系(或者说依赖关系)从多对多(网状关系)转为一对多(星状关系)。原来一个对象要跟 n 个对象交互,现在只需要跟一个中介对象交互,从而最小化对象之间的交互关系,降低了代码的复杂度,提高了代码的可读性和可维护性。

观察者模式和中介模式都是为了实现参与者之间的解耦,简化交互关系。两者的不同在于应用场景上。在观察者模式的应用场景中,参与者之间的交互比较有条理,一般都是单向的,一个参与者只有一个身份,要么是观察者,要么是被观察者。而在中介者模式的应用场景中,参与者之间的交互关系错综复杂,既可以是消息的发送者,也可以同时是消息的接收者。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值