第5章 行为型模式 - 算法和对象间通信 (Behavioral Patterns: Algorithms and Object Communication)
一、导读
1.1 本章学习目标
在本章结束时,你将能够:
- 理解行为型模式的核心目标,即如何有效地分配职责和管理对象之间的通信
- 运用责任链模式解耦请求发送者和接收者,支持动态处理流程
- 通过命令模式将请求封装为对象,支持撤销、队列和日志功能
- 使用解释器模式定义简单的语言语法并构建解释器
- 利用迭代器模式统一遍历不同集合的方式,隐藏内部实现
- 应用中介者模式简化多对多通信,降低对象间的耦合度
- 通过备忘录模式捕获和恢复对象状态,实现撤销功能
- 掌握观察者模式实现一对多的依赖关系,支持事件驱动架构
- 正确使用状态模式使对象在内部状态改变时改变其行为
- 运用策略模式封装可互换的算法族,实现运行时切换
- 通过模板方法模式定义算法骨架,让子类决定具体步骤
- 利用访问者模式在不修改元素类的前提下添加新操作
- 比较十一种行为型模式的适用场景,根据实际问题选择最合适的模式
1.2 为什么本章如此重要
如果说创建型模式关注对象的创建,结构型模式关注对象的组合,那么行为型模式关注的就是对象之间的交互和职责分配。在一个设计良好的系统中,对象不仅要正确地创建和组织,还要能够高效地协作和通信。
行为型模式解决的核心问题是:谁应该做什么?对象之间如何通信?职责如何分配? 这些问题直接决定了系统的灵活性、可扩展性和可维护性。
在实际开发中,我们经常面临这样的挑战:多个对象需要相互通信,导致复杂的依赖网络;算法需要在运行时切换;需要在不修改现有类的情况下添加新功能;需要记录对象的状态以便后续恢复;需要遍历集合并对每个元素执行操作。行为型模式为这些问题提供了经过验证的解决方案。
观察者模式是响应式编程和事件驱动架构的基础;策略模式使算法可以独立于使用它的客户端而变化;命令模式支持撤销/重做、宏录制、任务队列等功能;模板方法模式是框架设计的核心机制;访问者模式使得在不修改类层次的情况下添加新操作成为可能。
深入理解行为型模式,将使你有能力设计出更加灵活、松耦合的系统,能够优雅地应对需求变化和功能扩展。
1.3 核心问题引导
在阅读本章之前,请思考以下问题:
当多个对象需要相互通信时,如何避免形成复杂的网状依赖?中介者模式如何解决这个问题?
为什么将算法封装到独立的策略类中比使用条件语句更好?什么情况下应该选择策略模式?
命令模式与简单的方法调用有什么区别?封装为对象带来了哪些额外的好处?
观察者模式中,如果观察者的更新方法抛出异常会发生什么?如何处理这种情况?
状态模式与策略模式的结构非常相似,它们的本质区别是什么?
访问者模式为什么被称为"双分派"?它解决了什么问题?有什么局限性?
模板方法模式中的"好莱坞原则"是什么意思?它如何防止"依赖倒置"?
责任链模式中,如果没有任何处理器能处理请求怎么办?如何确保请求最终被处理或拒绝?
二、核心概念详解
5.1 责任链模式 (Chain of Responsibility)
责任链模式使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
意图:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
动机:考虑一个技术支持系统。用户提交的问题可能由一线客服、二线工程师或专家团队处理。问题的复杂度决定了由哪一级别处理。发送者(用户)不应该知道谁会处理他的问题,也不应该硬编码处理流程。
责任链模式将潜在的处理器连接成一条链。每个处理器持有下一个处理器的引用。当收到请求时,处理器判断自己是否能处理;如果能,就处理;否则,转发给下一个处理器。
UML结构描述:
Handler(处理器):定义一个处理请求的接口;可选地实现后继链接。ConcreteHandler(具体处理器):处理它所负责的请求;可访问它的后继者;如果可处理该请求,就处理之,否则将该请求转发给它的后继者。Client(客户端):向链上的具体处理器对象提交请求。
关键特征:
- 解耦发送者和接收者
- 动态组合处理流程
- 请求可能未被处理
两种类型:
- 纯责任链:请求要么被完全处理,要么不被处理,不会部分处理
- 不纯责任链:请求可能被多个处理器部分处理
适用场景:
- 有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定
- 你想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求
- 可处理一个请求的对象集合应被动态指定
5.2 命令模式 (Command)
命令模式将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。
意图:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。
动机:在GUI应用中,菜单项、按钮、快捷键都可能触发相同的操作。如果直接在事件处理器中编写业务逻辑,会导致代码重复且难以维护。此外,如果需要支持撤销功能,简单的函数调用无法回退操作。
命令模式将请求封装为对象,包含执行操作所需的所有信息。调用者只需知道如何执行命令,不需要了解具体的业务逻辑。命令可以被存储、排队、日志记录,并支持撤销。
UML结构描述:
Command(命令):声明执行操作的接口。ConcreteCommand(具体命令):将一个接收者对象绑定于一个动作;调用接收者相应的操作,以实现Execute。Client(客户端):创建一个具体命令对象并设定它的接收者。Invoker(调用者):要求该命令执行这个请求。Receiver(接收者):知道如何实施与执行一个请求相关的操作。
关键特征:
- 请求封装为对象
- 支持撤销/重做
- 支持宏命令(组合多个命令)
- 支持命令队列和日志
适用场景:
- 抽象出待执行的动作以参数化某对象
- 在不同的时刻指定、排列和执行请求
- 支持取消操作
- 支持修改日志
5.3 解释器模式 (Interpreter)
解释器模式给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
意图:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
动机:假设你需要解析一个简单的数学表达式语言,如"3 + 4 * 2"。你可以使用正则表达式和字符串处理来解析,但随着语言复杂度的增加,这种方法变得难以维护。解释器模式提供了一种结构化的方法来定义和解释语言。
解释器模式将语言的每条规则表示为一个类。解释器递归地解析输入,构建语法树,然后执行语法树中的节点来计算结果。
UML结构描述:
AbstractExpression(抽象表达式):声明一个抽象的解释操作,这个接口为抽象语法树中所有的节点所共享。TerminalExpression(终结符表达式):实现与文法中的终结符相关联的解释操作。NonterminalExpression(非终结符表达式):为文法中的非终结符实现解释操作。Context(上下文):包含解释器之外的一些全局信息。Client(客户端):构建表示该文法定义的语言中一个特定的句子的抽象语法树;调用解释操作。
关键特征:
- 将语法规则映射为类
- 构建语法树
- 易于扩展新规则
适用场景:
- 当有一个语言需要解释执行,并且你可将该语言中的句子表示为一个抽象语法树时
- 当一个语言的文法相对简单时
- 效率不是关键问题时
局限性:
- 对于复杂文法,类层次会变得庞大
- 效率较低
- 难以维护和调试
5.4 迭代器模式 (Iterator)
迭代器模式提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。
意图:提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。
动机:集合类(如列表、树、图)有不同的内部结构。如果客户端直接访问内部结构,就会与具体实现紧密耦合。此外,不同的遍历方式(前序、中序、后序)需要不同的实现。
迭代器模式将遍历逻辑封装在独立的迭代器对象中。集合提供创建迭代器的方法,客户端通过迭代器接口访问元素,无需了解集合的内部结构。
UML结构描述:
Iterator(迭代器):定义访问和遍历元素的接口。ConcreteIterator(具体迭代器):实现迭代器接口;对该聚合遍历时跟踪当前位置。Aggregate(聚合):定义创建相应迭代器对象的接口。ConcreteAggregate(具体聚合):实现创建相应迭代器的接口,该操作返回ConcreteIterator的一个适当的实例。
关键特征:
- 隐藏集合内部结构
- 统一的遍历接口
- 支持多种遍历方式
现代语言的支持:
Java的Iterable/Iterator接口、Python的__iter__/__next__方法、JavaScript的Symbol.iterator都原生支持迭代器模式。foreach循环语法糖简化了迭代器的使用。
适用场景:
- 访问一个聚合对象的内容而无需暴露它的内部表示
- 支持对聚合对象的多种遍历
- 为遍历不同的聚合结构提供一个统一的接口
5.5 中介者模式 (Mediator)
中介者模式用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
意图:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
动机:在GUI对话框中,多个控件(文本框、下拉框、按钮)之间可能存在复杂的交互逻辑。例如,选择一个选项可能启用某些按钮、禁用其他控件。如果每个控件都直接与其他控件通信,会形成复杂的网状依赖。
中介者模式引入一个中介对象,所有组件只与中介者通信。中介者负责协调组件之间的交互。这样,组件之间的多对多关系被简化为一对多关系(每个组件只与中介者通信)。
UML结构描述:
Mediator(中介者):定义一个接口用于与各同事对象通信。ConcreteMediator(具体中介者):通过协调各同事对象实现协作行为;了解并维护它的各个同事。Colleague(同事类):每个同事类都知道它的中介者对象;每个同事对象在需与其他的同事通信时,与它的中介者通信。
关键特征:
- 集中控制交互逻辑
- 将网状结构变为星形结构
- 中介者可能变得复杂
与外观模式的区别:
- 外观模式是对子系统的简化接口,子系统不知道外观的存在
- 中介者是同事对象之间的协调者,同事知道中介者的存在并与之交互
适用场景:
- 一组对象以定义良好但是复杂的方式进行通信
- 一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象
- 想定制一个分布在多个类中的行为,而又不想生成太多的子类
5.6 备忘录模式 (Memento)
备忘录模式在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
意图:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
动机:在文本编辑器中,用户可能需要撤销之前的操作。简单的做法是保存整个文档的副本,但这会消耗大量内存。更好的方法是只保存变化的部分,并且确保状态的保存和恢复不会破坏对象的封装性。
备忘录模式将对象的状态封装在备忘录对象中。发起人创建备忘录并保存状态, caretaker(管理者)负责存储备忘录,但不能访问其内容。当需要恢复时,发起人从备忘录中读取状态。
UML结构描述:
Memento(备忘录):存储原发器对象的内部状态;原发器根据需要决定备忘录存储原发器的哪些内部状态;防止原发器以外的其他对象访问备忘录。Originator(发起人):创建一个含有当前的内部状态的备忘录对象;使用备忘录对象存储其内部状态。Caretaker(管理者):负责保存好备忘录;不能对备忘录的内容进行操作或检查。
关键特征:
- 保持封装边界
- 窄接口和宽接口
- 可能消耗大量内存
实现考虑:
- 增量存储 vs 全量存储
- 备忘录的生命周期管理
- 序列化支持
适用场景:
- 必须保存一个对象在某一个时刻的(部分)状态,这样以后需要时它才能恢复到先前的状态
- 如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性
5.7 观察者模式 (Observer)
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
意图:定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
动机:在股票交易系统中,当股票价格变化时,所有订阅该股票的投资者都应该收到通知。如果使用轮询方式,投资者需要不断查询价格,效率低下且延迟高。观察者模式实现了推送机制,价格变化时主动通知所有订阅者。
UML结构描述:
Subject(主题):主题知道它的观察者。可以有任意多个观察者观察同一个主题;提供注册和删除观察者对象的接口。Observer(观察者):为那些在主题发生改变时需获得通知的对象定义一个更新接口。ConcreteSubject(具体主题):将有关状态存入各ConcreteObserver对象;当它的状态发生改变时,向它的各个观察者发出通知。ConcreteObserver(具体观察者):维护一个指向ConcreteSubject对象的引用;存储有关状态,这些状态应与目标的状态保持一致;实现Observer的更新接口以使自身状态与目标的状态保持一致。
关键特征:
- 一对多的依赖关系
- 自动通知
- 松耦合
推模型 vs 拉模型:
- 推模型:主题向观察者推送详细信息
- 拉模型:主题只发送最小通知,观察者主动从主题获取详细信息
注意事项:
- 避免循环依赖
- 注意线程安全
- 处理观察者更新时的异常
- 考虑异步通知
适用场景:
- 当一个抽象模型有两个方面,其中一个方面依赖于另一方面
- 当对一个对象的改变需要同时改变其它对象,而不知道具体有多少对象有待改变
- 当一个对象必须通知其它对象,而它又不能假定其它对象是谁
5.8 状态模式 (State)
状态模式允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
意图:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
动机:考虑一个TCP连接,它有三种状态:Established、Listening、Closed。在不同状态下,同样的操作(如open()、close())有不同的行为。如果使用条件语句判断状态,代码会变得复杂且难以维护。
状态模式将每种状态的行为封装在独立的状态类中。上下文持有当前状态的引用,并将请求委托给当前状态对象。状态转换可以由状态类或上下文控制。
UML结构描述:
Context(上下文):定义客户感兴趣的接口;维护一个ConcreteState子类的实例,这个实例定义当前状态。State(状态):定义一个接口以封装与Context的一个特定状态相关的行为。ConcreteState(具体状态):每一子类实现一个与Context的一个状态相关的行为。
关键特征:
- 状态与行为绑定
- 局部化状态转换逻辑
- 消除条件语句
与策略模式的区别:
- 策略模式中,客户端主动选择策略;状态模式中,状态转换通常是自动的
- 策略关注算法的可替换性;状态关注状态变化引起的行为变化
- 策略的各个实现通常互不关心;状态的转换可能有固定的顺序
适用场景:
- 一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为
- 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态
5.9 策略模式 (Strategy)
策略模式定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
意图:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
动机:在排序应用中,可能需要根据数据特点选择不同的排序算法:小数据集用插入排序,大数据集用快速排序,几乎有序的数据用归并排序。如果将排序算法硬编码在客户端,新增算法需要修改客户端代码。
策略模式将每个算法封装在独立的策略类中。客户端持有策略的引用,可以在运行时切换策略。新增算法只需添加新的策略类,无需修改客户端。
UML结构描述:
Strategy(策略):定义所有支持的算法的公共接口。ConcreteStrategy(具体策略):以Strategy接口实现某具体算法。Context(上下文):用一个ConcreteStrategy对象来配置;维护一个对Strategy对象的引用;可定义一个接口来让Strategy访问它的数据。
关键特征:
- 算法族可互换
- 消除条件语句
- 客户端选择策略
与状态模式的区别:见状态模式部分。
适用场景:
- 许多相关的类仅仅是行为有异
- 需要使用一个算法的不同变体
- 算法使用客户不应该知道的数据
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现
5.10 模板方法模式 (Template Method)
模板方法模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
动机:考虑一个数据分析框架,数据处理流程固定:加载数据、预处理、分析、生成报告。不同类型的分析(统计分析、机器学习)在预处理和分析步骤上有差异,但整体流程相同。
模板方法模式在基类中定义算法的骨架(模板方法),将可变步骤声明为抽象方法或钩子方法。子类实现这些方法,定制具体行为,但不改变算法的整体结构。
UML结构描述:
AbstractClass(抽象类):定义抽象的原语操作,具体的子类将重定义它们以实现一个算法的各步骤;实现一个模板方法,定义一个算法的骨架。ConcreteClass(具体类):实现原语操作以完成算法中与特定子类相关的步骤。
关键特征:
- 算法骨架固定
- 子类定制细节
- 好莱坞原则(Don't call us, we'll call you)
钩子方法:
钩子方法是可选覆盖的方法,有默认实现。子类可以选择性地覆盖钩子方法来定制行为,而不必覆盖所有方法。
适用场景:
- 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现
- 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复
- 控制子类扩展
5.11 访问者模式 (Visitor)
访问者模式表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
意图:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
动机:考虑一个编译器,需要对抽象语法树(AST)执行多种操作:类型检查、代码生成、优化等。如果将这些操作作为AST节点的方法,每次新增操作都需要修改所有节点类。
访问者模式将操作封装在独立的访问者类中。AST节点提供accept方法,接受访问者并调用访问者的visit方法。通过双分派(double dispatch),正确的visit方法被调用。新增操作只需添加新的访问者类,无需修改节点类。
UML结构描述:
Visitor(访问者):为该对象结构中ConcreteElement的每一个类声明一个Visit操作。ConcreteVisitor(具体访问者):实现每个由Visitor声明的操作。Element(元素):定义一个Accept操作,它以一个访问者为参数。ConcreteElement(具体元素):实现Accept操作,该操作以一个访问者为参数。ObjectStructure(对象结构):能枚举它的元素;可以提供一个高层的接口以允许该访问者访问它的元素。
关键特征:
- 双分派
- 易于添加新操作
- 难以添加新元素类
双分派解释:
第一次分派:元素调用visitor.visit(this),根据元素的实际类型选择正确的重载方法。
第二次分派:由于this的实际类型在运行时确定,所以根据元素类型和访问者类型共同确定调用的方法。
局限性:
- 添加新的元素类需要修改所有访问者
- 破坏封装(访问者需要访问元素的内部状态)
- 递归访问可能导致栈溢出
适用场景:
- 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作
- 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作"污染"这些对象的类
- 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作
三、程序员的视角
3.1 在现代框架中的应用
责任链模式在Servlet过滤器、Spring Security的过滤器链、Express.js的中间件中广泛应用。每个处理器决定是否处理请求或传递给下一个处理器。
命令模式在Redux的action/reducer、Java Swing的Action、Electron的菜单命令中体现。命令可以被序列化、持久化、重放。
迭代器模式在所有现代编程语言中原生支持。Java的Stream API、Python的生成器、JavaScript的Generator都是迭代器的扩展。
中介者模式在React的Context API、Vue的事件总线、聊天室的房间管理器中体现。中介者协调组件之间的通信。
观察者模式是事件驱动架构的核心。Node.js的EventEmitter、RxJS的Observable、Vue的响应式系统都基于观察者模式。
状态模式在Redux的状态机、XState库、工作流引擎中应用。状态转换逻辑被清晰地建模。
策略模式在Java的Comparator、Collections.sort()、Spring的ResourceLoader中体现。运行时选择算法。
模板方法模式是框架设计的核心。Spring的JdbcTemplate、JUnit的测试生命周期、React组件的生命周期方法都是模板方法的体现。
访问者模式在编译器的AST处理、Jackson的JSON序列化、IDE的代码分析中应用。
3.2 常见陷阱与误区
责任链的无限循环:如果链中存在循环引用,请求可能无限循环。确保链是有向无环的。
命令的粒度问题:命令太细导致历史栈过大,太粗导致撤销体验不佳。需要根据用户体验找到平衡。
解释器的性能问题:解释器模式效率较低,不适合性能敏感的场景。考虑编译为字节码或使用现有解析库。
迭代器的并发修改:在遍历过程中修改集合可能导致ConcurrentModificationException。使用fail-fast或fail-safe迭代器。
中介者的复杂性:中介者可能变得过于复杂,成为"上帝对象"。考虑将中介者分解为多个协作者。
备忘录的内存消耗:频繁保存状态可能消耗大量内存。使用增量存储或限制历史记录数量。
观察者的更新顺序:观察者的通知顺序可能影响结果。如果需要特定顺序,明确定义或使用优先级。
状态转换的可见性:状态转换可能发生在任何地方,难以追踪。使用状态机库或记录状态转换日志。
策略的选择逻辑:客户端如何选择策略?如果选择逻辑复杂,可以结合工厂模式。
模板方法的僵化:模板方法固定了算法结构,可能不够灵活。提供钩子方法增加灵活性。
访问者的双分派开销:双分派增加了间接层,可能影响性能。在性能敏感场景中谨慎使用。
3.3 与其他层级的联系
行为型模式与软件架构密切相关。观察者模式是事件驱动架构和响应式编程的基础;命令模式支持CQRS(命令查询职责分离);中介者模式在微服务编排中体现;策略模式支持插件架构。
在设计原则层面,行为型模式体现了多个原则:策略和模板方法体现了开闭原则;观察者体现了依赖倒置原则;命令体现了单一职责原则;中介者体现了迪米特法则。
四、代码实战
5.1 示例一:责任链模式实现日志处理
// 处理器接口
public abstract class LogHandler {
protected LogHandler nextHandler;
public LogHandler setNext(LogHandler handler) {
this.nextHandler = handler;
return handler;
}
public void handle(LogLevel level, String message) {
if (canHandle(level)) {
writeLog(level, message);
}
if (nextHandler != null) {
nextHandler.handle(level, message);
}
}
protected abstract boolean canHandle(LogLevel level);
protected abstract void writeLog(LogLevel level, String message);
}
// 日志级别
public enum LogLevel {
DEBUG, INFO, WARN, ERROR
}
// 具体处理器
public class ConsoleLogHandler extends LogHandler {
@Override
protected boolean canHandle(LogLevel level) {
return level == LogLevel.DEBUG || level == LogLevel.INFO;
}
@Override
protected void writeLog(LogLevel level, String message) {
System.out.println("[CONSOLE] " + level + ": " + message);
}
}
public class FileLogHandler extends LogHandler {
@Override
protected boolean canHandle(LogLevel level) {
return level == LogLevel.WARN;
}
@Override
protected void writeLog(LogLevel level, String message) {
// 写入文件
System.out.println("[FILE] " + level + ": " + message);
}
}
public class EmailLogHandler extends LogHandler {
@Override
protected boolean canHandle(LogLevel level) {
return level == LogLevel.ERROR;
}
@Override
protected void writeLog(LogLevel level, String message) {
// 发送邮件
System.out.println("[EMAIL] " + level + ": " + message);
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
LogHandler chain = new ConsoleLogHandler();
chain.setNext(new FileLogHandler())
.setNext(new EmailLogHandler());
chain.handle(LogLevel.DEBUG, "Debug message");
chain.handle(LogLevel.WARN, "Warning message");
chain.handle(LogLevel.ERROR, "Error message");
}
}5.2 示例二:命令模式实现遥控器
// 命令接口
public interface Command {
void execute();
void undo();
}
// 接收者
public class Light {
private String location;
public Light(String location) {
this.location = location;
}
public void on() {
System.out.println(location + " light is ON");
}
public void off() {
System.out.println(location + " light is OFF");
}
}
// 具体命令
public class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
@Override
public void undo() {
light.off();
}
}
public class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.off();
}
@Override
public void undo() {
light.on();
}
}
// 调用者
public class RemoteControl {
private Command[] onCommands;
private Command[] offCommands;
private Command lastCommand;
public RemoteControl() {
onCommands = new Command[7];
offCommands = new Command[7];
Command noCommand = new NoCommand();
for (int i = 0; i < 7; i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
}
public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void onButtonPressed(int slot) {
onCommands[slot].execute();
lastCommand = onCommands[slot];
}
public void offButtonPressed(int slot) {
offCommands[slot].execute();
lastCommand = offCommands[slot];
}
public void undoButtonPressed() {
lastCommand.undo();
}
}
// 空命令
public class NoCommand implements Command {
@Override
public void execute() {}
@Override
public void undo() {}
}
// 使用示例
public class Main {
public static void main(String[] args) {
RemoteControl remote = new RemoteControl();
Light livingRoomLight = new Light("Living Room");
Light kitchenLight = new Light("Kitchen");
remote.setCommand(0, new LightOnCommand(livingRoomLight),
new LightOffCommand(livingRoomLight));
remote.setCommand(1, new LightOnCommand(kitchenLight),
new LightOffCommand(kitchenLight));
remote.onButtonPressed(0);
remote.offButtonPressed(0);
remote.undoButtonPressed();
}
}5.3 示例三:观察者模式实现新闻订阅
// 主题接口
interface Subject {
addObserver(observer: Observer): void;
removeObserver(observer: Observer): void;
notifyObservers(): void;
}
// 观察者接口
interface Observer {
update(news: News): void;
}
// 新闻类
class News {
constructor(
public title: string,
public content: string,
public timestamp: Date
) {}
}
// 具体主题
class NewsAgency implements Subject {
private observers: Observer[] = [];
private latestNews: News | null = null;
addObserver(observer: Observer): void {
this.observers.push(observer);
}
removeObserver(observer: Observer): void {
const index = this.observers.indexOf(observer);
if (index > -1) {
this.observers.splice(index, 1);
}
}
notifyObservers(): void {
if (this.latestNews) {
for (const observer of this.observers) {
observer.update(this.latestNews);
}
}
}
publishNews(title: string, content: string): void {
this.latestNews = new News(title, content, new Date());
console.log(`Published: ${title}`);
this.notifyObservers();
}
}
// 具体观察者
class NewsChannel implements Observer {
private name: string;
constructor(name: string) {
this.name = name;
}
update(news: News): void {
console.log(`${this.name} received: "${news.title}" at ${news.timestamp.toLocaleTimeString()}`);
}
}
// 使用示例
const agency = new NewsAgency();
const cnn = new NewsChannel('CNN');
const bbc = new NewsChannel('BBC');
agency.addObserver(cnn);
agency.addObserver(bbc);
agency.publishNews('Breaking News', 'Something important happened!');5.4 示例四:策略模式实现支付处理
// 策略接口
public interface PaymentStrategy {
PaymentResult pay(double amount);
}
// 具体策略
public class AlipayStrategy implements PaymentStrategy {
@Override
public PaymentResult pay(double amount) {
System.out.println("Processing Alipay payment: $" + amount);
return new PaymentResult(true, "Alipay payment successful");
}
}
public class WechatPayStrategy implements PaymentStrategy {
@Override
public PaymentResult pay(double amount) {
System.out.println("Processing WeChat Pay payment: $" + amount);
return new PaymentResult(true, "WeChat Pay payment successful");
}
}
public class CreditCardStrategy implements PaymentStrategy {
private String cardNumber;
public CreditCardStrategy(String cardNumber) {
this.cardNumber = cardNumber;
}
@Override
public PaymentResult pay(double amount) {
System.out.println("Processing credit card payment: $" + amount);
return new PaymentResult(true, "Credit card payment successful");
}
}
// 结果类
public class PaymentResult {
private boolean success;
private String message;
public PaymentResult(boolean success, String message) {
this.success = success;
this.message = message;
}
public boolean isSuccess() { return success; }
public String getMessage() { return message; }
}
// 上下文
public class PaymentContext {
private PaymentStrategy strategy;
public PaymentContext(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void setStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
public PaymentResult executePayment(double amount) {
return strategy.pay(amount);
}
}
// 使用示例
public class CheckoutService {
public static void main(String[] args) {
PaymentContext context = new PaymentContext(new AlipayStrategy());
context.executePayment(100.0);
context.setStrategy(new WechatPayStrategy());
context.executePayment(200.0);
context.setStrategy(new CreditCardStrategy("1234-5678-9012-3456"));
context.executePayment(300.0);
}
}5.5 示例五:状态模式实现订单状态机
// 状态接口
public interface OrderState {
void pay(Order order);
void ship(Order order);
void deliver(Order order);
void cancel(Order order);
String getStateName();
}
// 具体状态
public class CreatedState implements OrderState {
@Override
public void pay(Order order) {
System.out.println("Payment processed");
order.setState(new PaidState());
}
@Override
public void ship(Order order) {
System.out.println("Cannot ship unpaid order");
}
@Override
public void deliver(Order order) {
System.out.println("Cannot deliver unpaid order");
}
@Override
public void cancel(Order order) {
System.out.println("Order cancelled");
order.setState(new CancelledState());
}
@Override
public String getStateName() {
return "Created";
}
}
public class PaidState implements OrderState {
@Override
public void pay(Order order) {
System.out.println("Already paid");
}
@Override
public void ship(Order order) {
System.out.println("Order shipped");
order.setState(new ShippedState());
}
@Override
public void deliver(Order order) {
System.out.println("Cannot deliver unshipped order");
}
@Override
public void cancel(Order order) {
System.out.println("Refund processed, order cancelled");
order.setState(new CancelledState());
}
@Override
public String getStateName() {
return "Paid";
}
}
public class ShippedState implements OrderState {
@Override
public void pay(Order order) {
System.out.println("Already paid");
}
@Override
public void ship(Order order) {
System.out.println("Already shipped");
}
@Override
public void deliver(Order order) {
System.out.println("Order delivered");
order.setState(new DeliveredState());
}
@Override
public void cancel(Order order) {
System.out.println("Cannot cancel shipped order, initiate return");
}
@Override
public String getStateName() {
return "Shipped";
}
}
public class DeliveredState implements OrderState {
@Override
public void pay(Order order) {
System.out.println("Already paid");
}
@Override
public void ship(Order order) {
System.out.println("Already shipped");
}
@Override
public void deliver(Order order) {
System.out.println("Already delivered");
}
@Override
public void cancel(Order order) {
System.out.println("Cannot cancel delivered order, initiate return");
}
@Override
public String getStateName() {
return "Delivered";
}
}
public class CancelledState implements OrderState {
@Override
public void pay(Order order) {
System.out.println("Cannot pay cancelled order");
}
@Override
public void ship(Order order) {
System.out.println("Cannot ship cancelled order");
}
@Override
public void deliver(Order order) {
System.out.println("Cannot deliver cancelled order");
}
@Override
public void cancel(Order order) {
System.out.println("Already cancelled");
}
@Override
public String getStateName() {
return "Cancelled";
}
}
// 上下文
public class Order {
private OrderState state;
private String orderId;
public Order(String orderId) {
this.orderId = orderId;
this.state = new CreatedState();
}
public void setState(OrderState state) {
this.state = state;
}
public void pay() {
state.pay(this);
}
public void ship() {
state.ship(this);
}
public void deliver() {
state.deliver(this);
}
public void cancel() {
state.cancel(this);
}
public String getStatus() {
return state.getStateName();
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
Order order = new Order("ORD-001");
System.out.println(order.getStatus()); // Created
order.pay();
System.out.println(order.getStatus()); // Paid
order.ship();
System.out.println(order.getStatus()); // Shipped
order.deliver();
System.out.println(order.getStatus()); // Delivered
}
}5.6 示例六:模板方法模式实现数据分析框架
// 抽象类
public abstract class DataAnalyzer {
// 模板方法
public final void analyze(String dataSource) {
loadData(dataSource);
preprocess();
Map<String, Object> results = performAnalysis();
generateReport(results);
}
// 具体方法
protected void loadData(String dataSource) {
System.out.println("Loading data from: " + dataSource);
}
// 抽象方法
protected abstract void preprocess();
protected abstract Map<String, Object> performAnalysis();
// 钩子方法
protected void generateReport(Map<String, Object> results) {
System.out.println("Generating standard report...");
for (Map.Entry<String, Object> entry : results.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
// 具体类
public class StatisticalAnalyzer extends DataAnalyzer {
@Override
protected void preprocess() {
System.out.println("Removing outliers and normalizing data");
}
@Override
protected Map<String, Object> performAnalysis() {
Map<String, Object> results = new HashMap<>();
results.put("mean", 42.5);
results.put("median", 40.0);
results.put("stdDev", 5.2);
return results;
}
@Override
protected void generateReport(Map<String, Object> results) {
System.out.println("=== Statistical Analysis Report ===");
super.generateReport(results);
}
}
public class MLAnalyzer extends DataAnalyzer {
@Override
protected void preprocess() {
System.out.println("Feature scaling and encoding categorical variables");
}
@Override
protected Map<String, Object> performAnalysis() {
Map<String, Object> results = new HashMap<>();
results.put("accuracy", 0.95);
results.put("precision", 0.93);
results.put("recall", 0.91);
return results;
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
DataAnalyzer statAnalyzer = new StatisticalAnalyzer();
statAnalyzer.analyze("database://sales");
System.out.println("---");
DataAnalyzer mlAnalyzer = new MLAnalyzer();
mlAnalyzer.analyze("database://customers");
}
}5.7 示例七:访问者模式实现AST处理
// 访问者接口
public interface ASTVisitor {
void visit(NumberNode node);
void visit(AdditionNode node);
void visit(MultiplicationNode node);
}
// 元素接口
public interface ASTNode {
void accept(ASTVisitor visitor);
}
// 具体元素
public class NumberNode implements ASTNode {
private double value;
public NumberNode(double value) {
this.value = value;
}
public double getValue() {
return value;
}
@Override
public void accept(ASTVisitor visitor) {
visitor.visit(this);
}
}
public class AdditionNode implements ASTNode {
private ASTNode left;
private ASTNode right;
public AdditionNode(ASTNode left, ASTNode right) {
this.left = left;
this.right = right;
}
public ASTNode getLeft() {
return left;
}
public ASTNode getRight() {
return right;
}
@Override
public void accept(ASTVisitor visitor) {
visitor.visit(this);
}
}
public class MultiplicationNode implements ASTNode {
private ASTNode left;
private ASTNode right;
public MultiplicationNode(ASTNode left, ASTNode right) {
this.left = left;
this.right = right;
}
public ASTNode getLeft() {
return left;
}
public ASTNode getRight() {
return right;
}
@Override
public void accept(ASTVisitor visitor) {
visitor.visit(this);
}
}
// 具体访问者:求值
public class EvaluationVisitor implements ASTVisitor {
private double result;
public double getResult() {
return result;
}
@Override
public void visit(NumberNode node) {
result = node.getValue();
}
@Override
public void visit(AdditionNode node) {
node.getLeft().accept(this);
double leftValue = result;
node.getRight().accept(this);
double rightValue = result;
result = leftValue + rightValue;
}
@Override
public void visit(MultiplicationNode node) {
node.getLeft().accept(this);
double leftValue = result;
node.getRight().accept(this);
double rightValue = result;
result = leftValue * rightValue;
}
}
// 具体访问者:打印
public class PrintVisitor implements ASTVisitor {
private StringBuilder output = new StringBuilder();
public String getOutput() {
return output.toString();
}
@Override
public void visit(NumberNode node) {
output.append(node.getValue());
}
@Override
public void visit(AdditionNode node) {
output.append("(");
node.getLeft().accept(this);
output.append(" + ");
node.getRight().accept(this);
output.append(")");
}
@Override
public void visit(MultiplicationNode node) {
output.append("(");
node.getLeft().accept(this);
output.append(" * ");
node.getRight().accept(this);
output.append(")");
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
// 构建AST: (3 + 4) * 2
ASTNode ast = new MultiplicationNode(
new AdditionNode(new NumberNode(3), new NumberNode(4)),
new NumberNode(2)
);
// 求值
EvaluationVisitor evalVisitor = new EvaluationVisitor();
ast.accept(evalVisitor);
System.out.println("Result: " + evalVisitor.getResult()); // 14.0
// 打印
PrintVisitor printVisitor = new PrintVisitor();
ast.accept(printVisitor);
System.out.println("Expression: " + printVisitor.getOutput()); // ((3.0 + 4.0) * 2.0)
}
}五、深入思考
5.1 历史背景
行为型模式的概念源于对小talk和C++项目中反复出现的交互模式的研究。GoF发现,许多设计问题本质上是如何分配职责和管理对象通信的问题。
观察者模式受到Simula和Smalltalk中事件处理机制的启发;命令模式源于GUI框架中的动作处理;策略模式来自算法族的封装需求;模板方法模式是框架设计的自然结果;访问者模式受到双分派概念的启发。
这些模式的整理和命名,使得开发者能够用统一的词汇讨论复杂的交互设计问题。
5.2 未解决的问题
行为型模式与响应式编程:RxJS、Project Reactor等响应式库提供了强大的数据流处理能力。传统的观察者模式如何与响应式编程结合?行为型模式在异步环境下的适用性如何?
分布式系统中的行为模式:在微服务架构中,对象通信变成了服务间通信。责任链、中介者、观察者等模式如何适应分布式环境?
函数式编程的影响:高阶函数、柯里化、组合子等函数式特性是否可以替代某些行为型模式?策略模式是否可以用函数替代?
AI辅助的模式识别:能否开发工具自动识别代码中可以应用的行为型模式?AI能否建议更优的职责分配方案?
性能与抽象的权衡:行为型模式增加了间接层,可能影响性能。在对延迟极度敏感的场景中,如何平衡设计的优雅性和执行效率?
5.3 延伸阅读
"Head First Design Patterns" by Eric Freeman & Elisabeth Robson - 以生动有趣的方式讲解行为型模式。
"Clean Code" by Robert C. Martin - 从代码质量角度讨论如何应用行为型模式改进设计。
"Design Patterns in Ruby" by Russ Olsen - 展示行为型模式在Ruby中的实现,特别是如何用块和Proc简化模式。
"Functional Programming in Java" by Venkat Subramaniam - 探讨如何用函数式特性替代某些设计模式。
"Reactive Programming with RxJS" by Sergi Mansilla - 深入理解观察者模式在现代响应式编程中的应用。
六、实验笔记
实验一:实现一个事件驱动的任务调度系统
目标:综合运用多种行为型模式,实现一个灵活的任务调度系统。
要求:
使用观察者模式实现任务完成事件的通知机制
使用策略模式支持不同的调度策略(FIFO、优先级、时间片轮转)
使用命令模式封装任务,支持撤销和重试
使用责任链模式实现任务处理的流水线
使用状态模式管理任务的生命周期(待执行、执行中、已完成、失败)
步骤:
设计完整的类图和接口
实现核心功能
编写测试用例
添加监控和日志功能
预期结果:一个可扩展的任务调度框架,支持自定义调度策略和处理流程。
实验二:分析开源项目中的行为型模式
目标:研究一个知名开源项目的源码,识别其中使用的行为型模式。
候选项目:
- Redux(JavaScript)
- Spring Framework(Java)
- Vue.js(JavaScript)
- Express.js(Node.js)
任务:
阅读项目的核心代码
识别至少4种行为型模式
分析这些模式的具体实现方式
评估设计的优缺点
提出改进建议
预期结果:一份详细的分析报告,包含模式识别、代码片段和设计评价。
七、本章小结
本章详细介绍了十一种行为型模式,它们从不同角度解决了对象间通信和职责分配的问题:
责任链模式解耦请求发送者和接收者,支持动态的处理流程配置。
命令模式将请求封装为对象,支持撤销、队列、日志等高级功能。
解释器模式为简单语言提供结构化的解析方法,适合领域特定语言的实现。
迭代器模式统一了集合遍历的方式,隐藏了内部实现细节。
中介者模式简化多对多通信,将网状结构变为星形结构。
备忘录模式在不破坏封装的前提下捕获和恢复对象状态。
观察者模式实现一对多的依赖关系,是事件驱动架构的基础。
状态模式使对象在内部状态改变时改变其行为,消除复杂的条件语句。
策略模式封装可互换的算法族,使算法独立于客户端变化。
模板方法模式定义算法骨架,让子类定制具体步骤,是框架设计的核心。
访问者模式在不修改元素类的前提下添加新操作,适合稳定的对象结构。
这些模式的核心思想是合理地分配职责和管理通信。好的职责分配使系统更加灵活、易于理解和维护。
在实际应用中,行为型模式经常组合使用。例如,命令模式可以与备忘录模式结合实现撤销功能;观察者模式可以与策略模式结合实现不同的通知策略;模板方法模式可以与策略模式结合提供更大的灵活性。
理解行为型模式不仅有助于设计更好的交互逻辑,还能帮助你更好地理解现代框架的设计思想。无论是React的状态管理、Spring的事件机制,还是RxJS的响应式编程,都能看到行为型模式的影子。
八、习题
基础题
画出观察者模式的UML类图,并解释推模型和拉模型的区别。
策略模式与状态模式的结构非常相似,它们的本质区别是什么?各举一个典型应用场景。
命令模式中,Invoker、Command、Receiver三者之间的关系是什么?为什么需要这三层分离?
模板方法模式中的"好莱坞原则"是什么意思?它如何帮助防止依赖倒置?
访问者模式为什么被称为"双分派"?解释双分派的工作机制。
进阶题
设计一个在线购物系统的订单处理流程。使用状态模式管理订单状态,使用责任链模式处理订单验证(库存检查、信用检查、欺诈检测),使用观察者模式通知相关方(仓库、物流、用户)。
实现一个简单的SQL查询解释器。支持SELECT、FROM、WHERE子句的解析和执行。使用解释器模式构建语法树,使用访问者模式执行查询和打印SQL。
分析Java Collections Framework中的迭代器实现。为什么Iterator接口没有add方法?ListIterator与Iterator有什么区别?
挑战题
设计一个工作流引擎,支持可视化编排业务流程。使用命令模式封装每个工作流节点,使用状态模式管理工作流实例的状态,使用观察者模式实现事件通知,使用策略模式支持不同的路由规则。给出完整的类设计和核心代码实现,考虑并发执行和持久化需求。
研究Redux Saga或RxJS的源码。分析它们如何结合命令模式、观察者模式和策略模式来实现复杂的异步流程管理。写一篇短文,比较传统面向对象实现与响应式编程实现的异同,探讨行为型模式在函数式和响应式范式中的演化。