目录
行为型模式用于描述多个类或对象之间的协作方式,它涉及算法与对象间职责的分配。
行为型模式分为类行为模式和对象行为模式。
类行为模式采用继承机制来在类间分派行为,
对象行为模式采用组合或聚合在对象间分配行为。
由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。
行为型模式是 GoF 设计模式中最为庞大的一类,它包含以下 11 种模式。除了模板方法模式和解释器模式是类行为型模式,其他的全部属于对象行为型模式。
模式:模板方法模式(Template Method)
类图:
一句话总结:
定义一个操作中的算法骨架,将算法的一些步骤延迟到子类中,使得子类在可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
优点:
1,它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
2,它在父类中提取了公共的部分代码,便于代码复用。
3,部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
缺点:
1,对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
2,父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
案例描述:
游戏其中一种UI框架:抽象UI逻辑(View)模板方法,子类继承并实现自己的具体方法。
应用场景:
1,算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
2,当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
3,当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。
模式:策略模式(Strategy) – 分装策略
类图:
一句话总结:
定义一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的改变不会影响使用算法的客户。
优点:
1,多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句。
2,策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
3,策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
4,策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
5,策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
缺点:
1,客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
2,策略模式造成很多的策略类。
案例描述:
游戏技能的目标选择算法:多种选取目标的方法(直线,扇形,圆形等)
应用场景:
1,一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
2,一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
3,系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
4,系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
5,多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。
模式:命令模式(Command)
类图:
一句话总结:
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
优点:
1,降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
2,增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
3,可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
4,方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
缺点:
1,可能产生大量具体命令类。因为计对每一个具体操作都需要设计一个具体命令类,这将增加系统的复杂性。
案例描述:
游戏GM指令系统的实现。
服务员下单给厨房厨师。
应用场景:
1,当系统需要将请求调用者与请求接收者解耦时,命令模式使得调用者和接收者不直接交互。
2,当系统需要随机请求命令或经常增加或删除命令时,命令模式比较方便实现这些功能。
3,当系统需要执行一组操作时,命令模式可以定义宏命令来实现该功能。
4,当系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作时,可以将命令对象存储起来,采用备忘录模式来实现。
模式:职责链模式(Chain of Responsibility)
类图:
一句话总结:
把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
优点:
1,降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
2,增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
3,增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
4,责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
5,责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
缺点:
1,不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
2,对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
3,职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
案例描述:
游戏关卡系统(满足条件才能下一关,爬塔,无尽模式等)
请假审批模块
应用场景:
1,有多个对象可以处理一个请求,哪个对象处理该请求由运行时刻自动确定。
2,可动态指定一组对象处理请求,或添加新的处理者。
3,在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求。
模式:状态模式(State) – 分装状态
类图:
一句话总结:
状态模式的解决思想是:当控制一个对象状态转换的条件表达式过于复杂时,把相关“判断逻辑”提取出来,放到一系列的状态类当中,这样可以把原来复杂的逻辑判断简单化。
优点:
1,状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
2,减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
3,有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。
缺点:
1,状态模式的使用必然会增加系统的类与对象的个数。
2,状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
案例描述:
状态机
应用场景:
1,当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
2,一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。
模式:观察者模式(Observer)
类图:
一句话总结:
指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。又称作发布-订阅模式。
优点:
1,降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
2,目标与观察者之间建立了一套触发机制。
缺点:
1,目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
2,当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
案例描述:
MVC 模式中的模型与视图的关系
游戏事件系统
游戏协议处理系统
应用场景:
1,对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
2,当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
模式:中介者模式(Mediator) – 分装交互
类图:
一句话总结:
定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。
优点:
1,降低了对象之间的耦合性,使得对象易于独立地被复用。
2,将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。
缺点:
1,当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。
案例描述:
在 MVC 框架中,控制器(C)就是模型(M)和视图(V)的中介者
通讯录
棋牌游戏每局中,一个中介来负责处理各位牌友之间的交互(赢输钱)。
应用场景:
1,当对象之间存在复杂的网状结构关系而导致依赖关系混乱且难以复用时。
2,当想创建一个运行于多个类之间的对象,又不想生成新的子类时。
模式:迭代器模式(Iterator)
类图:
一句话总结:
提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。(想要遍历我的对象,通过迭代器类给你)
大数情况下使用语言中已有的聚合类的迭代器就已经够了
优点:
1,访问一个聚合对象的内容而无须暴露它的内部表示。
2,遍历任务交由迭代器完成,这简化了聚合类。
3,它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。
4,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
5,封装性良好,为遍历不同的聚合结构提供一个统一的接口。
缺点:
1,增加了类的个数,这在一定程度上增加了系统的复杂性。
案例描述:
c# Dictionary
应用场景:
1,当需要为聚合对象提供多种遍历方式时。
2,当需要为遍历不同的聚合结构提供一个统一的接口时。
3,当访问一个聚合对象的内容而无须暴露其内部细节的表示时。
模式:访问者模式(Visitor)
类图:
一句话总结:
白话文:访问者模式对相同的数据(数据结构要稳定的),做不同的处理(访问)操作,且不影响原数据(结构)。
原定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
优点:
1,扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
2,复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
3,灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
4,符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
缺点:
1,增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
2,破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
3,违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。
案例描述:
一般不会用到。
数据结构是人, 分为男人和女人时,是稳定的。但两个的行为方式却是不一样的(操作算法经常变化)。
应用场景:
1,对象结构相对稳定,但其操作算法经常变化的程序。
2,对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
3,对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。
模式:备忘录模式(Memento)
类图:
一句话总结:
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。
备忘录模式的核心是设计备忘录类以及用于管理备忘录的管理者类。
优点:
1,提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
2,实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
3,简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
缺点:
1,资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。
案例描述:
Ctrl+Z
棋类游戏中的悔棋
存档
应用场景:
1,需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。
2,需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,Eclipse 等软件在编辑时按 Ctrl+Z 组合键,还有数据库中事务操作。
模式:解释器模式
类图:
一句话总结:
用的非常少,而且会引起效率、性能以及维护等问题。
几种相似模式的比较
1】外观模式,代理模式,中介者模式比较
中介者使用前
中介者使用后