第10章 · 播客 第3集

设计模式 · 行为型模式

🎙️ 本集播客第3集:GoF 行为型 11 个模式逐个对意图,重点拆 Mediator 中介者与 Observer 观察者——attach、detach、notify、update 四方法分工、中介者的优点缺点与 GUI 场景,外加 State 与 Strategy 的区分和两个广播坑。 语音由微软 Edge 神经网络语音预生成(女声·晓伊,男声·云希)。点击下方任意对话可直接从该句开始播,当前句朗读时下一句已预先加载,无缝衔接。
🎙️ 第10章播客 第3集
⬇ 下载本集 点击播放
阿明
上一集结构型讲完了,今天该轮到行为型了吧?
小雅
对。先把三类模式的分工摆正:创建型解决对象创建的问题,结构型解决类与对象怎么组织,行为型回答的是第三问——系统中的类与对象如何相互通信。行为型是 GoF 23 个模式里数量最多的一类,一共 11 个。
小雅
名单先过一遍,意图全按原文来。Interpreter 解释器:定义了一个解释器,来解释遵循给定语言和文法的句子。Template Method 模板方法:定义一个操作的模板,其中的一些步骤会在子类中实现,以适应不同的情况。Chain of Responsibility 职责链:把可以响应请求的对象组织成一条链,并在这条对象链上传递请求,保证多个对象都有机会处理请求,还避免了请求方和响应方的耦合。
小雅
Command 命令模式:将请求封装为对象,从而增强请求的能力,比如参数化、排队、记录日志。Iterator 迭代器:提供顺序访问一个对象集合中各元素的方法,避免暴露集合中对象的耦合关系。Mediator 中介者:减少系统中对象间的耦合性。Memento 备忘录:提供一种捕获对象状态的方法,且不会破坏对象的封装,可以在对象外部保存状态并在需要时恢复。Observer 观察者:将对象的状态广播到一组观察者,让每个观察者随时得到更新的通知。State 状态:允许一个对象在其内部状态改变的时候改变它的行为。Strategy 策略:让对象中算法的变化独立于客户。Visitor 访问者:在不改变各元素类的前提下定义作用于这些元素的新操作。
阿明
十一个一口气念完,我脑子已经成职责链了。State 和 Strategy 听着最像。
小雅
用定义切:Strategy 管的是算法,让算法的变化独立于客户;State 管的是状态,内部状态一变,对象的行为就跟着变。题目里看到「算法」选 Strategy,看到「状态改变行为」选 State。
阿明
这 11 个里,教材在第二节细讲的是哪几个?
小雅
两个,Mediator 和 Observer,也是本章行为型的主要出题点。先讲 Mediator,又称中介者模式。
小雅
它解决的问题:复杂系统中对象相互通信,造成相互依赖,修改其中一个对象可能影响若干其他对象,系统混乱且难以理解、可维护性下降。Mediator 的思路是通过封装一组对象间的通信,为系统解耦。
小雅
结构上,一组对象——也就是抽象类 Colleague 的子类——通过中介类 ConcreteMediator 进行通信;中介类继承抽象类 Mediator,实现其中定义的通信接口,保证中介类和相互通信类之间接口的一致。
阿明
这跟消息队列那种消息机制,是不是一回事?
小雅
原文专门区分了:Mediator 看上去类似消息机制,其实区别很大——它不需要负责消息的排队、优先级等处理,但必须根据 Colleague 发送的消息作出响应,并向特定的 Colleague 类发送消息。原文有个很形象的比喻:ConcreteMediator 就好比人的大脑,接收到眼睛发送的「前面有一个坑」的消息,就向脚发送「停止前进」的消息。
阿明
同一条消息,走路时回「停止前进」,开车时就该回「踩刹车」了。
小雅
没错,这正是它降耦的效果:Mediator 封装了一组对象间的通信,降低了 Colleague 之间的耦合性,单个 Colleague 的变化不会影响到其他对象。应用场景原文点名的是 GUI:比如窗体或表单里有多个互相通信的对象,显示人员的下拉列表要根据部门下拉列表的不同选择而变化,用 Mediator 封装通信能降低耦合、提高可维护性。
阿明
那缺点呢?我感觉没有白降的耦合。
小雅
问得好,这正是考题:Mediator 的主要缺点是什么?答案是——当 Colleague 较多且关系复杂时,中介类会变得非常复杂、难以维护。希赛教育专家提示把机理说透了:每一个需要通信的对象都需要知道 Mediator 的存在,Mediator 也需要知道每一个通信对象的实现,所以一般仅在较小的范围内使用,否则过多的 Colleague 造成中介类难以维护,会抵消该模式带来的优点,反而让系统更难以维护。
小雅
做这道题时把干扰项也过一遍:「无法降低 Colleague 之间的耦合性」——错,降耦合恰恰是它的优点;「不适用于 GUI 开发」——错,GUI 正是它的典型应用;「需要为每个对象创建中介」——也错,中介是一组对象共用的一个,不是人手一个。正确答案就是中介类会变复杂难维护那条。
阿明
中介是把网状通信收拢到一点,代价是这一点自己变成蜘蛛网。
小雅
总结得比我好。接下来是本集重头 Observer,观察者模式。它解决的问题:对象封装特性使状态变化各自独立,A 对象状态变化不会立即反映到 B 对象;但很多对象是相互关联的,比如股票行情系统,股票状态变化要同时反映到多个视图——实时行情、技术分析图表。硬编码这些关联会造成紧密耦合,系统难以维护和复用。
小雅
Observer 的解法是把一对多的对象依赖转化为抽象耦合。结构上,抽象类 Subject 定义主题类——也就是被观察者的接口,它的子类 ConcreteSubject 的任何状态改变都会通知全部观察者;观察者们为了保持接口一致,继承自相同的抽象类 Observer。
阿明
考题来了:Subject 通知所有观察者的方法是哪个?
小雅
notify 方法。这四个方法的分工必须一个不错:Subject 里保存观察者列表 observers,attach 方法动态添加一个特定的观察者,detach 方法移出一个观察者,而 notify 是主题类根据目前加入订阅的观察者列表,向每一个观察者发出状态改变的消息。选项里常见的干扰 update——那是观察者一方收到通知后执行的动作,不是 Subject 的通知方法。
小雅
协作链路串一遍:ConcreteSubject 的 setState 方法表明对象状态发生了变化,该方法会调用 notify 方法,对所有观察者进行更新。所以顺序是 setState 触发 notify,notify 再逐个调用观察者的 update。
阿明
订阅的时候,观察者知道还有别的观察者吗?
小雅
不知道,这是效果的精髓:采用订阅方法添加的观察者并不知道还有其他的观察者,它仅仅能够接收被观察主题对象的状态改变。反过来,Subject 了解目前有哪些观察者要捕获自己的状态变化,但不了解这些观察者要做什么。这样一来,动态地增加和移除观察者都非常简单。
阿明
听起来像消息广播。广播没坏处吗?
小雅
有,原文说广播的副作用是增加系统负载:任何消息都将被自动发送到每一个观察者,每一个观察者都根据这些消息作出响应,而这些响应未必都是必要的。应用方面,Smalltalk 的 MVC 架构就应用了 Observer 模式,把 Model 的变化传递到 View 中;前面架构那节也提过,消息总线的架构风格同 Observer 模式有神似之处。
阿明
两个坑我想确认:一个是连环 setState 会怎样,一个是广播会不会失控。
小雅
第一个:客户程序很可能执行了 setState1 之后紧接着执行 setState2,甚至连续多次更改状态,这样每一次更改都会带来一个 notify 事件,这些更改被放大到每一个 Observer;而且并发执行观察者的 update 可能造成程序错误,独占访问 update 又会造成程序阻塞。
小雅
对策是另一种发送方式:客户程序在执行了一系列改变对象状态的方法之后再调用 notify,就避免了上面的问题。但这种方法有两个代价——首先不能保证对象状态的变化一定会被通知到各个观察者,其次造成观察者对客户程序的不透明。所以使用 Observer 时要精确定义对象状态变化的依赖关系。
小雅
第二个坑更狠:广播是一柄双刃剑,要像避免以太网的广播风暴那样避免广播扩大。在 update 方法中要避免更新 Subject 类的状态,尤其不能更新自己观察的 Subject 类的状态,否则可能造成对象间的消息无穷循环下去。
阿明
Mediator 和 Observer 都是解耦对象通信的,放一起我有点晕:到底怎么选?
小雅
看通信方向和知情权。Mediator 是双向协商:Colleague 相互之间要通信,把路由全交给中介,Colleague 只认识中介;Observer 是单向广播:Subject 状态变了,单向推给一组观察者,观察者之间互不知情。一句话——对象们要互相打交道找中介,一个状态要通知一堆人找观察者。
小雅
再把它们和结构型里易混的划清界限:题目问「以下哪个属于结构型」,正确项会是 Adapter 适配器这类;Observer、Command 都是行为型,Singleton 是创建型。判断题的套路就是三类各抽一个混在选项里,拿分类口诀挡:创建管生、结构管组织、行为管通信。
阿明
本集必背给我串一遍。
小雅
必背清单:第一,行为型管的是类与对象如何相互通信,GoF 行为型共 11 个。第二,Subject 通知所有观察者的是 notify 方法,attach 添加观察者、detach 移出观察者,setState 调用 notify,观察者侧执行 update。第三,Mediator 的缺点是 Colleague 多且关系复杂时中介类变得非常复杂、难以维护。降 Colleague 间耦合和 GUI 应用是它的优点与场景,不是缺点。
小雅
第四,观察者之间互不知情,Subject 也不知道观察者要做什么;广播副作用是系统负载,连环 setState 会放大 notify,update 里不能更新自己观察的 Subject,否则消息无穷循环。第五,Strategy 管算法独立变化,State 管内部状态改变行为。
阿明
「创建管生、结构管组织、行为管通信」,这句我记住了。下一集讲什么?
小雅
J2EE 那批模式——Session Facade、Intercepting Filter,再加上设计模式的使用原则:为什么不能滥用、为什么说重构也是应用设计模式的好时机。那也是本章考题的最后一波。