设计模式之行为模式


创建型模式(5个)
工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

(记忆:抽象想象方法建造一个原来单一工厂

结构型模式(7个)
适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式

行为模式(11个)
策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

所有的设计模式都是为了程序能更好的满足这六大原则。设计模式一共有 23 种,今天我们先来学习行为模式,一共十一种,分别是:

  • 策略模式、
  • 模板方法模式、
  • 观察者模式、
  • 迭代器模式、
  • 责任链模式、
  • 命令模式、
  • 备忘录模式、
  • 状态模式、
  • 访问者模式、
  • 中介者模式、
  • 解释器模式。

一、策略模式

什么是策略模式
在这里插入图片描述

在这里插入图片描述
策略模式也有点像三国演义中诸葛亮给刘关张的锦囊;

  • 第⼀个锦囊:⻅乔国⽼,并把刘备娶亲的事情du搞得东吴⼈尽皆知。
  • 第⼆个锦囊:⽤谎⾔(曹操打荆州)骗泡在温柔乡⾥的刘备回去。
  • 第三个锦囊:让孙夫⼈摆平东吴的追兵,她是孙权妹妹,东吴将领惧她三分。

策略(Strategy)模式的定义:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理

策略模式的基本结构

  • 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
  • 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
  • 环境(Context)类:持有一个策略类的引用,最终给客户端调用

策略模式的基本代码实现

抽象策略类

在这里插入图片描述
具体策略类A

在这里插入图片描述
具体策略类B

在这里插入图片描述
环境类

在这里插入图片描述

  • 在这里其实也可以设置为构造函数,接受对象。

测试类
在这里插入图片描述


策略模式的优缺点

优点

  • 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句,如 if…else 语句、switch…case 语句。
  • 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
  • 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
  • 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
  • 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。

其主要缺点如下。

  • 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
  • 策略模式造成很多的策略类,增加维护难度。

二、模板方法模式

模板方法模式的简介
在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。

例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。

模板方法(Template Method)模式的定义如下:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤


模板方法模式基本代码结构

抽象类
在这里插入图片描述
具体子类
在这里插入图片描述
测试类
在这里插入图片描述


模板方法的优缺点

优点

  • 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
  • 它在父类中提取了公共的部分代码,便于代码复用。
  • 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。

缺点

  • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。
  • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
  • 由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。

三、观察者模式

在这里插入图片描述
观察者模式的简介

在现实世界中,许多对象并不是独立存在的,其中一个对象的行为发生改变可能会导致一个或者多个其他对象的行为也发生改变。例如,某种商品的物价上涨时会导致部分商家高兴,而消费者伤心;还有,当我们开车到交叉路口时,遇到红灯会停,遇到绿灯会行。这样的例子还有很多,例如,股票价格与股民、微信公众号与微信用户、气象局的天气预报与听众、小偷与警察等。

观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。


观察者的基本结构

  • 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
  • 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
  • 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
  • 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。

观察者的基本代码结构

抽象目标
在这里插入图片描述
具体目标
在这里插入图片描述

抽象观察者

在这里插入图片描述
具体观察者1
在这里插入图片描述
具体观察者2
在这里插入图片描述

测试类
在这里插入图片描述


案例场景模拟

下面举一个具体实例,假设上班时间有一部分同事在看股票,一部分同事在看NBA,这时老板回来了,前台通知了部分同事老板回来了,这些同事及时关闭了网页没被发现,而没被通知到的同事被抓了个现行,被老板亲自“通知”关闭网页,UML图如下:

代码演示

通知者接口(也就是前台和老板,他们具有让观察者操作的方法)
在这里插入图片描述

观察者(也就是摸鱼的程序员)

在这里插入图片描述

通知者的实现

前台Secretary和老板Boss作为具体通知者,实现Subject接口。这里只给出Secretary类的代码,Boss类与之类似。
在这里插入图片描述
观察者的实现

StockObserver是看股票的同事,NBAObserver是看NBA的同事,作为具体观察者,继承Observer类。这里只给出StockObserver类的代码,NBAObserver类与之类似。
在这里插入图片描述
测试

前台作为通知者进行通

在这里插入图片描述


观察者模式的优缺点

优点

  • 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
  • 目标与观察者之间建立了一套触发机制。

缺点

  • 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
  • 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。

四、迭代器模式

在这里插入图片描述


迭代器模式的简介

在现实生活以及程序设计中,经常要访问一个聚合对象中的各个元素,如“数据结构”中的链表遍历,通常的做法是将链表的创建和遍历都放在同一个类中,但这种方式不利于程序的扩展,如果要更换遍历方法就必须修改程序源代码,这违背了 “开闭原则”。

既然将遍历方法封装在聚合类中不可取,那么聚合类中不提供遍历方法,将遍历方法由用户自己实现是否可行呢?答案是同样不可取,因为这种方式会存在两个缺点:
暴露了聚合类的内部表示,使其数据不安全;
增加了客户的负担。

“迭代器模式”能较好地克服以上缺点,它在客户访问类与聚合类之间插入一个迭代器,这分离了聚合对象与其遍历行为,对客户也隐藏了其内部细节,且满足“单一职责原则”和“开闭原则”,如 Java 中的 Collection、List、Set、Map 等都包含了迭代器

迭代器(Iterator)模式的定义:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示

在日常开发中,我们几乎不会自己写迭代器。除非需要定制一个自己实现的数据结构对应的迭代器,否则,开源框架提供的 API 完全够用。


迭代器模式的基本结构

  • 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。
  • 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
  • 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。
  • 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。

迭代器模式的基本代码结构

抽象聚合

在这里插入图片描述
具体聚合

在这里插入图片描述

抽象迭代器
在这里插入图片描述
具体迭代器
在这里插入图片描述
测试类
在这里插入图片描述


迭代器模式的优缺点

优点

  • 访问一个聚合对象的内容而无须暴露它的内部表示。
  • 遍历任务交由迭代器完成,这简化了聚合类。
  • 它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。
  • 增加新的聚合类和迭代器类都很方便,无须修改原有代码。
  • 封装性良好,为遍历不同的聚合结构提供一个统一的接口
    缺点
  • 增加了类的个数,这在一定程度上增加了系统的复杂性。

五、责任链模式

在这里插入图片描述

责任链模式的简介

在现实生活中,一个事件需要经过多个对象处理是很常见的场景。例如,采购审批流程、请假流程等。公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据需要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这无疑增加了难度。

责任链模式的基本结构

  • 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
  • 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
  • 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

责任链模式的代码基本结构

抽象处理者角色
在这里插入图片描述
具体处理者角色1
在这里插入图片描述
具体处理者角色2
在这里插入图片描述

测试类。
在这里插入图片描述


项目需求

  • 员工请假,先找组长审批,然后给经理,最后给老板审批同意才行。

责任链代码演示

审核请求 => 对应请求角色
在这里插入图片描述

抽象处理者

在这里插入图片描述

具体处理者,小组长

在这里插入图片描述

具体处理者,项目经理
在这里插入图片描述

具体处理者,老板
在这里插入图片描述

测试类

在这里插入图片描述

责任链模式的优缺点

优点

  • 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
  • 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
  • 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
  • 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
  • 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则

缺点

  • 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
  • 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
  • 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用

六、命令模式

在软件开发系统中,“方法的请求者”与“方法的实现者”之间经常存在紧密的耦合关系,这不利于软件功能的扩展与维护。例如,想对方法进行“撤销、重做、记录”等处理都很不方便,因此“如何将方法的请求者与实现者解耦?”变得很重要,命令模式就能很好地解决这个问题。

在现实生活中,命令模式的例子也很多。比如看电视时,我们只需要轻轻一按遥控器就能完成频道的切换,这就是命令模式,将换台请求和换台处理完全解耦了。电视机遥控器(命令发送者)通过按钮(具体命令)来遥控电视机(命令接收者)。

再比如,我们去餐厅吃饭,菜单不是等到客人来了之后才定制的,而是已经预先配置好的。这样,客人来了就只需要点菜,而不是任由客人临时定制。餐厅提供的菜单就相当于把请求和处理进行了解耦,这就是命令模式的体现

命令(Command)模式的定义如下:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。


命令模式的基本结构

  • 抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法 execute()。
  • 具体命令类(Concrete Command)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
  • 实现者/接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者。
  • 调用者/请求者(Invoker)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者

命令模式的基本代码结构

抽象命令

在这里插入图片描述
接受者
在这里插入图片描述
具体命令
在这里插入图片描述
调用者
在这里插入图片描述
测试类
在这里插入图片描述


案例场景模拟
在这里插入图片描述
(思考下:怎么感觉这描述和代理模式好像,看完后大家思考下)

  • 像是这样⼀个复杂的场景,如果不知道设计模式直接开发,也是可以达到⽬的的。但对于后续的各项的菜品扩展、厨师实现以及如何调⽤上会变得⾮常耦合难以扩展。

像是这样⼀个复杂的场景,如果不知道设计模式直接开发,也是可以达到⽬的的。但对于后续的各项的
菜品扩展、厨师实现以及如何调⽤上会变得⾮常耦合难以扩展。


重构前得代码实现

在这里插入图片描述

  • 在这个类的实现中提供了两个⽅法,⼀个⽅法⽤于点单添加菜品 order() ,另外⼀个⽅法展示菜品的信息 placeOrder() 。
  • 从上⾯可以看到有⽐较多的if语句判断类型进⾏添加菜品,那么对于这样的代码后续就需要⼤量的经历进⾏维护,同时可能实际的逻辑要⽐这复杂的多。都写在这样⼀个类⾥会变得耦合的⾮常严格。

命令模式重构代码在这里插入图片描述

抽象命令定义(菜品接⼝)

在这里插入图片描述

⼴东(粤菜)

在这里插入图片描述

江苏(苏菜)
在这里插入图片描述

⼭东(鲁菜)

在这里插入图片描述

四川(川菜)
在这里插入图片描述

  • 以上是四种菜品的实现,在实现的类中都有添加了⼀个厨师类( ICook ),并通过这个类提供的⽅法
    进⾏操作命令(烹饪菜品) cook.doCooking() 。
  • 命令的实现过程可以是按照逻辑进⾏添加补充,⽬前这⾥抽象的⽐较简单,只是模拟⼀个烹饪的过
    程,相当于同时厨师进⾏菜品烹饪

抽象实现者定义(厨师接⼝)

在这里插入图片描述

  • 这⾥定义的是具体的为命令的实现者,这⾥也就是菜品对应的厨师烹饪的指令实现

实现者具体实现(四类厨师)

粤菜,厨师

在这里插入图片描述

苏菜,厨师

在这里插入图片描述

鲁菜,厨师

在这里插入图片描述

苏菜,厨师

在这里插入图片描述

这⾥是四类不同菜品的厨师,在这个实现的过程是模拟打了⽇志,相当于通知了厨房⾥具体的
厨师进⾏菜品烹饪。

从以上可以看到,当我们需要进⾏扩从的时候是可以⾮常⽅便的进⾏添加的,每⼀个类都具备了单
⼀职责原则。


调⽤者(⼩⼆)

在这里插入图片描述

在调⽤者的具体实现中,提供了菜品的添加和菜单执⾏烹饪。这个过程是命令模式的具体调⽤,通
过外部将菜品和厨师传递进来⽽进⾏具体的调⽤。


编写测试

在这里插入图片描述

  • 这⾥可以主要观察 菜品 与 厨师 的组合; new GuangDoneCuisine(new GuangDongCook()); ,
    每⼀个具体的命令都拥有⼀个对应的实现类,可以进⾏组合。
  • 当菜品和具体的实现定义完成后,由⼩⼆进⾏操作点
    单, xiaoEr.order(guangDoneCuisine); ,这⾥分别添加了四种菜品,给⼩⼆。
  • 最后是下单,这个是具体命令实现的操作,相当于把⼩⼆⼿⾥的菜单传递给厨师。当然这⾥也可以
    提供删除和撤销,也就是客户取消了⾃⼰的某个菜品

总结
在这里插入图片描述


命令模式的优缺点

优点。

  • 通过引入中间件(抽象接口)降低系统的耦合度。
  • 扩展性良好,增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,且满足“开闭原则”。
  • 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
  • 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
  • 可以在现有命令的基础上,增加额外功能。比如日志记录,结合装饰器模式会更加灵活。

其缺点是:

  • 可能产生大量具体的命令类。因为每一个具体操作都需要设计一个具体命令类,这会增加系统的复杂性。
  • 命令模式的结果其实就是接收方的执行结果,但是为了以命令的形式进行架构、解耦请求与实现,引入了额外类型结构(引入了请求方与抽象命令接口),增加了理解上的困难。不过这也是设计模式的通病,抽象必然会额外增加类的数量,代码抽离肯定比代码聚合更加难理解。

七、备忘录模式

备忘录模式的基本介绍

每个人都有犯错误的时候,都希望有种“后悔药”能弥补自己的过失,让自己重新开始,但现实是残酷的。在计算机应用中,客户同样会常常犯错误,能否提供“后悔药”给他们呢?当然是可以的,而且是有必要的。这个功能由“备忘录模式”来实现。

备忘录(Memento)模式的定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。


备忘录模式的基本结构

  • 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
  • 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
  • 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。

备忘录模式的基本代码结构

备忘录
在这里插入图片描述
发起人
在这里插入图片描述
管理者
在这里插入图片描述
测试类
在这里插入图片描述


真实案例场景演示

在这里插入图片描述
备忘录模式的项目结构
在这里插入图片描述
在这里插入图片描述


备忘录模式的代码实现

在这里插入图片描述

配置类可以是任何形式的,这⾥只是简单的描述了⼀个基本的配置内容信息

在这里插入图片描述

记录者类

在这里插入图片描述

  • 记录者类除了对 ConfigFile 配置类增加了获取和设置⽅法外,还增加了保存 saveMemento() 、获取 getMemento(ConfigMemento memento) 。
  • saveMemento :保存备忘录的时候会创建⼀个备忘录信息,并返回回去,交给管理者处理。
  • getMemento :获取的之后并不是直接返回,⽽是把备忘录的信息交给现在的配置⽂件this.configFile ,这部分需要注意

管理员类

在这里插入图片描述

  • 在这个类中主要实现的核⼼功能就是记录配置⽂件信息,也就是备忘录的效果,之后提供可以回滚和获取的⽅法,拿到备忘录的具体内容。
  • 同时这⾥设置了两个数据结构来存放备忘录,实际使⽤中可以按需设置。 List 、 Map<String, ConfigMemento> 。
  • 最后是提供的备忘录操作⽅法;存放( append )、回滚( undo )、返回( redo )、定向获取( get ),这
    样四个操作⽅法。

编写测试类

在这里插入图片描述

  • 这个设计模式的学习有⼀部分重点点是体现在了单元测试类上,这⾥包括了四次的信息存储和备忘录
    历史配置操作。
  • 通过上⾯添加了四次配置后,下⾯分别进⾏操作是; 回滚1次 、 再回滚1次 , 之后向前进1次 , 最
    后是获取指定的版本配置 。具体的效果可以参考测试结果

备忘录模式的优缺点
优点

  • 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
  • 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
  • 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。

缺点

  • 资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。

八、中介模式

中介者模式的介绍

在现实生活中,常常会出现好多对象之间存在复杂的交互关系,这种交互关系常常是“网状结构”,它要求每个对象都必须知道它需要交互的对象。例如,每个人必须记住他(她)所有朋友的电话;而且,朋友中如果有人的电话修改了,他(她)必须让其他所有的朋友一起修改,这叫作“牵一发而动全身”,非常复杂。

模式的定义与特点:定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。


中介模式的基本结构

  • 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
  • 具体中介者(Concrete Mediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
  • 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
  • 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。

中介模式的基本代码结构实现

抽象中介者
在这里插入图片描述
具体中介者
在这里插入图片描述
抽象同事类
在这里插入图片描述
具体中介者
在这里插入图片描述
抽象同事类

在这里插入图片描述
具体同事类
在这里插入图片描述

具体同事类在这里插入图片描述
测试类
在这里插入图片描述

具体
我们还是回到开篇讲到的那个例子。

关于租房中介的例子。加入没有租房中介。这里有六个房东需要出租房屋,房屋各不相同、各有各的特点、适合不一样的人租住。这六个房东之间刚好有点联系。然后都在出租房屋。这时租客A来租房,看了一号房东的房子不满意、但是一号房东觉得可以让他去看看其他五个朋友的房间。然后开始联系他的五个朋友。这样运行下去好像有是没有什么问题的。

但是其中二号房东如果房屋不出租或者出租出去了。也就是发生了变化。这是他则需要通知其他五个朋友,告诉他们不用再给他介绍租客了。这里就造成了中间一个人发生了变化,需要改动其他五个人。

那么如何可以解决这种情况呢。我们回到现在。把中介加进来、那六个房东都把房屋交给中介处理。租客A看房间一不满意直接通过中介看房间二。当房间二被出租了。中介也只需要通知房东二号、然后他们签订合同。下次还有人看房间就不会再看房间二了。这里一个出现了变化也就影响改变了一个。并不会影响其他另外五个房东。这个例子可以更好的帮助我们理解中介者模式。我们下面图。

在这里插入图片描述
抽象中介者

抽象中介者角色定义统一的接口,用于各同事角色之间的通信。
在这里插入图片描述


抽象同事类

每一个同事角色都知道中介者角色,而且与其它的同事角色通信的时候,一定要通过中介者角色协作。
每个同事类的行为分两种:一种是同事本身行为,比如改变对象本身的状态,处理自己的行为等,这种行为叫做自发行为,与其它同事类或者中介者没有任何依赖;

第二种是必须依赖中介者才能完成的行为,叫做依赖方法。

在这里插入图片描述


具体中介者类

在这里插入图片描述


具体同事类
在这里插入图片描述


测试类

在这里插入图片描述


中介者模式的优缺点
优点

  • 类之间各司其职,符合迪米特法则。
  • 降低了对象之间的耦合性,使得对象易于独立地被复用。
  • 将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。

缺点

  • 中介者模式将原本多个对象直接的相互依赖变成了中介者和多个同事类的依赖关系。当同事类越多时,中介者就会越臃肿,变得复杂且难以维护。

九、状态模式

状态模式的基本介绍

  • 在软件开发过程中,应用程序中的部分对象可能会根据不同的情况做出不同的行为,我们把这种对象称为有状态的对象,而把影响对象行为的一个或多个动态变化的属性称为状态。当有状态的对象与外部事件产生互动时,其内部状态就会发生改变,从而使其行为也发生改变。如人都有高兴和伤心的时候,不同的情绪有不同的行为,当然外界也会影响其情绪变化。

  • 对这种有状态的对象编程,传统的解决方案是:将这些所有可能发生的情况全都考虑到,然后使用 if-else 或 switch-case 语句来做状态判断,再进行不同情况的处理。但是显然这种做法对复杂的状态判断存在天然弊端,条件判断语句会过于臃肿,可读性差,且不具备扩展性,维护难度也大。且增加新的状态时要添加新的 if-else 语句,这违背了“开闭原则”,不利于程序的扩展。

  • 以上问题如果采用“状态模式”就能很好地得到解决。状态模式的解决思想是:当控制一个对象状态转换的条件表达式过于复杂时,把相关“判断逻辑”提取出来,用各个不同的类进行表示,系统处于哪种情况,直接使用相应的状态类对象进行处理,这样能把原来复杂的逻辑判断简单化,消除了 if-else、switch-case 等冗余语句,代码更有层次性,并且具备良好的扩展力。

  • 状态(State)模式的定义:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为


状态模式的基本结构

  • 环境类(Context)角色:也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。
  • 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。
  • 具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换

状态模式的基本代码结构

环境类

在这里插入图片描述
抽象状态类
在这里插入图片描述
具体状态A类

在这里插入图片描述

具体状态B类

在这里插入图片描述

测试类

在这里插入图片描述


案例代码演示

分析:本实例包含了“不及格”“中等”和“优秀” 3 种状态,当学生的分数小于 60 分时为“不及格”状态,当分数大于等于 60 分且小于 90 分时为“中等”状态,当分数大于等于 90 分时为“优秀”状态,我们用状态模式来实现这个程序。

环境类
在这里插入图片描述
抽象状态类
在这里插入图片描述

具体状态类:不及格
在这里插入图片描述

具体状态类:中等
在这里插入图片描述
具体状态类:优秀在这里插入图片描述
在这里插入图片描述


状态模式的优缺点

优点

  • 结构清晰,状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
  • 将状态转换显示化,减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态- - 转换变得更加明确,且减少对象间的相互依赖。
  • 状态类职责明确,有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。

缺点

  • 状态模式的使用必然会增加系统的类与对象的个数。
  • 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
  • 状态模式对开闭原则的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需- - 要修改那些负责状态转换的源码,否则无法切换到新增状态,而且修改某个状态类的行为也需要修改对应类的源码。

十、访问者模式

  • 在现实生活中,有些集合对象存在多种不同的元素,且每种元素也存在多种不同的访问者和处理方式。例如,公园中存在多个景点,也存在多个游客,不同的游客对同一个景点的评价可能不同;医院医生开的处方单中包含多种药元素,査看它的划价员和药房工作人员对它的处理方式也不同,划价员根据处方单上面的药品名和数量进行划价,药房工作人员根据处方单的内容进行抓药。

  • 这样的例子还有很多,例如,电影或电视剧中的人物角色,不同的观众对他们的评价也不同;还有顾客在商场购物时放在“购物车”中的商品,顾客主要关心所选商品的性价比,而收银员关心的是商品的价格和数量。

  • 这些被处理的数据元素相对稳定而访问方式多种多样的数据结构,如果用“访问者模式”来处理比较方便。访问者模式能把处理方法从数据结构中分离出来,并可以根据需要增加新的处理方法,且不用修改原来的程序代码与数据结构,这提高了程序的扩展性和灵活性。

  • 访问者(Visitor)模式的定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。


访问者模式的基本结构

  • 抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
  • 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
  • 抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
  • 具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
  • 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。

访问者模式基本代码结构

抽象访问者
在这里插入图片描述
具体访问者在这里插入图片描述
抽象元素类
在这里插入图片描述
具体元素
在这里插入图片描述
对象结构角色
在这里插入图片描述
测试类
在这里插入图片描述


访问者模式优缺点

优点

  • 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
  • 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
  • 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
  • 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。

缺点

  • 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在 - 每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
  • 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
  • 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。

十一、解释器模式

解释器模式的基本介绍
在软件开发中,会遇到有些问题多次重复出现,而且有一定的相似性和规律性。如果将它们归纳成一种简单的语言,那么这些问题实例将是该语言的一些句子,这样就可以用“编译原理”中的解释器模式来实现了。

虽然使用解释器模式的实例不是很多,但对于满足以上特点,且对运行效率要求不是很高的应用实例,如果用解释器模式来实现,其效果是非常好的,本文将介绍其工作原理与使用方法。

解释器(Interpreter)模式的定义:给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。


解释器模式的基本结构

解释器模式包含以下主要角色。

  • 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
  • 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
  • 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
  • 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
  • 客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法

解释器模式的基本代码结构在这里插入图片描述
模式的应用实例

说明:假如“韶粵通”公交车读卡器可以判断乘客的身份,如果是“韶关”或者“广州”的“老人” “妇女”“儿童”就可以免费乘车,其他人员乘车一次扣 2 元。

在这里插入图片描述
输出结果

您是韶关的老人,您本次乘车免费!
韶关的年轻人,您不是免费人员,本次乘车扣费2元!
您是广州的妇女,您本次乘车免费!
您是广州的儿童,您本次乘车免费!
山东的儿童,您不是免费人员,本次乘车扣费2元!


解释器模式的优缺点

优点

  • 扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
  • 容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。

解释器模式的主要缺点如下。

  • 执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。
  • 会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。
  • 可应用的场景比较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值