跳到内容

设计模式(行为型)

Canyue
发布日期:
7 分钟阅读

引言

设计模式(Design Pattern)是软件开发中经过验证的最佳实践,用于解决在软件设计中常见的问题。

经典设计模式可分为三种:

类型关注点
创建型对象怎么来
结构型对象怎么组装
行为型(本章节)对象间怎么协作

行为型最多,先从这里讲起

责任链

将对象连成一条链,沿着链传递请求,直到被一个对象处理为止

举个栗子:你要请假,但不同的请假时长需要由不同级别的领导审批

请假 1~3 天  →  直属组长审批
请假 4~7 天  →  部门经理审批
请假 8~15 天 →  总监审批
请假 15 天以上 →  CEO 审批

先来看图

假条会沿着责任链依次往下,组长批不了就部门经理批,还批不了就总监批

组长 -> 部门经理 -> 总监 -> CEO

在责任链中,发送者和请求者解耦,发送者无需考虑是谁处理了请求,只需将请求丢到链中

每个处理者职责单一,若自己处理不了,就向下游丢

责任链符合开闭原则,即使需要在链中增减处理者,对已有代码影响很小

命令

将请求封装为一个对象

今天你突然想吃汉堡,来到了蟹老板的餐厅

对于顾客,你只是找到章鱼哥(Invoker)点了一份蟹堡,等待一会蟹堡就做好了

在此过程中,顾客(Client) new了一份 订单(Command)的对象

章鱼哥(Invoker)仅持有订单对象,执行对象的execute(),将其交由后厨(Recever)

后厨海绵宝宝(Recever)根据订单内容执行,订单上是什么就做什么

可以看出,在命令模式中:

解释器

提供语言的语法或表达式,使用解释器表达解释该语言的句子

喝奶茶的时候,不知道各位有没有注意过标签

我们简化下 珍3/糖0.5/奶250/热水至500

聪明的你应该大致能看出来,三分珍珠、半糖、奶25ml、剩下的热水加满到500ml

你理解这段语言的过程,就是一个解释器,来看图 :

迭代器

允许顺序访问一个聚合对象的元素,同时不暴露其内部表示

现在应该很少人看电视了,你有没有发现,观众只需要提供遥控器的上下键,就能顺序切换节目,而观众无需关注电视中到底是怎么存储的

而你手中的遥控器,就可以简单理解成是一个迭代器,来看图

在程序中,通常还会实现这两个方法:

来段伪代码举例:

┌─────────────────────────────┐
│  while not isDone():        │  ← 1. 还没到头吗?
│      item = currentItem()   │  ← 2. 拿到当前元素
│      处理(item)              │  ← 3. 对元素做操作
│      next()                 │  ← 4. 指针往后挪一步
└─────────────────────────────┘

中介者(Mediator)

用一个对象封装一系列对象交互,使对象间不需要显式的相互引用

想象一下,要是没人发明工作群这个东西,平时要怎么和同事之间互相沟通

团队五个人(A-E),两两私聊,就有10条沟通线

A↔B、A↔C、A↔D、A↔E、B↔C、B↔D、B↔E、C↔D、C↔E、D↔E

厚礼蟹,这也太麻烦了。好,现在聪明的你拉了个微信群,当起了群主,将其他4名同时都拉了进来

大家再也不用互相传递消息,有事在群里一发就好

,就是中介者(Mediator),看图:

各个对象不再互相引用,而是把所有交互逻辑集中到一个中介者对象里,由它来协调、转发、仲裁。

对于对象间存在复杂的网状引用有奇效

备忘录

在不破坏封装性的情况下,捕获并在外部保存对象的状态

大家打游戏的时候,有没有被同一个BOSS虐到崩溃的经历

通常游戏会在进入BOSS战前,游戏会有一个自动存档,每当被BOOS击败的时候,会自动读回之前的状态

这个存档,就是备忘录,来看图:

观察者

当一个对象的状态发生改变,所有依赖它的对象都自动更新

这个好理解,各位应该都在B站或是别的平台,收到关注的博主的更新消息吧

先来看图:

博主(Subject)定义三个方法,attach、detach分别用于关注、取关;

notify方法用于在更新作品后,向所有的粉丝推送通知

class BiliUPHost(UPHost):
    # ........

    def notify(self, video_title: str):
        """发新视频,逐个推送给所有粉丝"""
        self._latest_video = video_title
        for fan in self._fans:
            fan.update(video_title, self.name)  # 调用粉丝的方法,发生通知

观察者模式最大的特点就是,使用发布-订阅,将两者送耦合

现实中通常使用MQ实现这种发布-订阅的模式

状态

同一个对象,状态不同,行为就不同

同一个”你”,在不同时间段(状态)下,面对同一个问题——“老板叫你”,反应完全不同:

时间段(状态)老板叫你,你的反应(行为)
刚睡醒”嗯?谁?……哦,闹钟。
挤地铁假装没听见,戴上耳机
在上班”好的老板,马上处理!
干饭中心里骂骂咧咧,含糊地说”稍等稍等”
下班后”您拨打的用户已关机。

来看图:

状态模式将状态以及执行的步骤进行了解耦,每单增删一个状态以及其行为时,只需要在实现、删除一个状态的实现即可,比直接写一堆IF语句优雅多了

# 不用状态模式的话
class Worker:
    def boss_call(self):
        if self.state == "睡觉":
            print("嗯?谁?")
        elif self.state == "挤地铁":
            print("假装没听见")
        elif self.state == "上班":
            print("好的老板!")
        elif self.state == "干饭":
            print("稍等稍等")
        elif self.state == "下班":
            print("您拨打的用户已关机")
        # ... 每加一个状态,这里就要加一个 elif

策略

将一系列算法封装成一个个类,并在需要时可相互替换

其实策略和状态两个模式在结构上几乎一样

我换个例子,通常我们在导航软件上,会有多条不同策略的路线选择(最短/最快/不走高速)

我们来看图,就能发起和状态模式及其相似:

似曾相识,对喽,我画图都直接只要CV改改

这两模式主要体现在意图不同,结构和作用上几乎是相同的

状态模式策略模式
核心意图状态变了,行为自动跟着变算法可以互相替换
状态之间知道彼此,能触发切换(下班→睡觉)互不知道,彼此独立
谁切换通常由状态自己决定下一个状态由客户端主动选择用哪个策略

模板方法

搭一个骨架,子类需重写方法实现

无论是炒青菜还是炒牛肉,整体的流程是相同的,简化完无非就四步:

  1. 洗食材
  2. 切食材
  3. 下锅炒
  4. 装盘上桌

但是不同的食材,每一步的具体做法可能有差异

步骤炒青菜炒牛肉
洗菜叶洗牛肉
切段切薄片、腌制
大火快炒2分钟猛火爆炒30秒
装盘直接装盘摆盘撒葱花

这时候,就可以先定义个一接口,然后在分别实现不同的做法

其实模板方法这一模式很简单且常用,看图:

访问者

元素的执行算法可随着访问者的改变而改变

东西(数据结构)是固定的,但要在它身上做的事(操作)经常变。于是把”做什么”抽出来,做成一个个独立的”访问者”

就像快递分拣中心,一天内会收到很多包裹,不同类型的包裹需要不同的处理方式

包裹类型特点
普通件随便扔,耐摔
生鲜件必须走冷链,超时作废
易碎件轻拿轻放,贴”向上”标签

而且同一个包裹,会经过不同人的手

即使包裹种类相对固定,但操作员可能会有变动增加

这时候就不适合将逻辑都塞到包裹类中,不然每加一个操作就要改所有包裹类,越改越臃肿。

访问者模式的做法是-包裹不动,让不同的人上门来”访问”它。看图:

该模式在新增操作极其容易:加一个 Inspector(质检员)访问者,一个包裹类都不用改

但反过来,新增元素类型(比如加个”危险品包裹”)就很痛苦:要改 Visitor 接口和所有已有访问者


参考资料:

《软件设计师教程》 - 清华大学出版社

《您的设计模式》 - 设计模式公司

一些AI给出的建议

上一篇
设计模式(结构型)
下一篇
简单讲讲KMP算法