面向对象设计口诀:变化的抽接口,相同的建模版
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。
行为型模式是 GoF 设计模式中最为庞大的一类,它包含以下 11 种模式。
模板方法(Template Method)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。
该模式的主要优点如下。
该模式的主要缺点如下。
模板方法模式需要注意抽象类与具体子类之间的协作。它用到了虚函数的多态性技术以及“不用调用我,让我来调用你”的反向控制技术。现在来介绍它们的基本结构。(钩子函数)
模板方法模式包含以下主要角色。
抽象模板类,负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。
① 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
② 基本方法:是整个算法中的一个步骤,包含以下几种类型。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uU6uRR8C-1624686657987)(…/images/设计模式-行为型/image-20210523192039358.png)]
模板方法模式通常适用于以下场景。
模板方法在AQS种的应用:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7BT7CCPk-1624686657988)(…/images/设计模式-行为型/image-20210523195109750.png)]
命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
在现实生活中,命令模式的例子也很多。比如看电视时,我们只需要轻轻一按遥控器就能完成频道的切换,这就是命令模式,将换台请求和换台处理完全解耦了。电视机遥控器(命令发送者)通过按钮(具体命令)来遥控电视机(命令接收者)。
再比如,我们去餐厅吃饭,菜单不是等到客人来了之后才定制的,而是已经预先配置好的。这样,客人来了就只需要点菜,而不是任由客人临时定制。餐厅提供的菜单就相当于把请求和处理进行了解耦,这就是命令模式的体现。
命令模式的主要优点如下。
其缺点是:
可以将系统中的相关操作抽象成命令,使调用者与实现者相关分离,其结构如下。
命令模式包含以下主要角色。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zd0TeAel-1624686657995)(…/images/设计模式-行为型/image-20210615011411901.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w32xDYcI-1624686657997)(…/images/设计模式-行为型/image-20210615022642780.png)]
将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
访问者模式主要应用场景是:需要对一个对象结构中的对象进行很多不同操作(这些操作彼此没有关联),同时需要避免让这些操作"污染"这些对象的类,可以选用访问者模式解决
访问者(Visitor)模式是一种对象行为型模式,其主要优点如下。
访问者(Visitor)模式的主要缺点如下。
访问者(Visitor)模式实现的关键是如何将作用于元素的操作分离出来封装成独立的类。
访问者模式包含以下主要角色。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vKTvadTH-1624686657999)(…/images/设计模式-行为型/image-20210615025832015.png)]
观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
观察者模式是一种对象行为型模式,其主要优点如下。
它的主要缺点如下。
实现观察者模式时要注意具体目标对象和具体观察者对象之间不能直接调用,否则将使两者之间紧密耦合起来,这违反了面向对象的设计原则。
观察者模式的主要角色如下。
example1:
在这个小游戏里,有一个迷宫,迷宫里有怪物、陷阱和宝物
一旦主角移动到怪物的有效范围,怪物会袭击主角;主角移动到陷阱的有效范围,陷阱会困住主角;主角移动到宝物的有效范围,宝物会为主角加血。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8dS1fRqC-1624686658001)(…/images/设计模式-行为型/image-20210615180316843.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B1PgwYQs-1624686658002)(…/images/设计模式-行为型/image-20210615181416696.png)]
在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。
Observable 类是抽象目标类,它有一个 Vector 向量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。
Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 void update(Observable o,Object arg) 方法,进行相应的工作。
Example2
分析:当原油价格上涨时,空方伤心,多方局兴;当油价下跌时,空方局兴,多方伤心。本实例中的抽象目标(Observable)类在 Java 中已经定义,可以直接定义其子类,即原油期货(OilFutures)类,它是具体目标类,该类中定义一个 SetPriCe(float price) 方法,当原油数据发生变化时调用其父类的 notifyObservers(Object arg) 方法来通知所有观察者;另外,本实例中的抽象观察者接口(Observer)在 Java 中已经定义,只要定义其子类,即具体观察者类(包括多方类 Bull 和空方类 Bear),并实现 update(Observable o,Object arg) 方法即可
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c3HLNGmY-1624686658003)(…/images/设计模式-行为型/image-20210615225414295.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nkdh5J2F-1624686658005)(…/images/设计模式-行为型/image-20210615230748581.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-363lCS5k-1624686658006)(…/images/设计模式-行为型/image-20210615231130865.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yp555RUC-1624686658007)(…/images/设计模式-行为型/image-20210615232552270.png)]
java的事件机制一般包括三个部分:EventObject,EventListener和Source。
java.util.EventObject是事件状态对象的基类,它封装了事件源对象以及和事件相关的信息。所有java的事件类都需要继承该类。
java.util.EventListener是一个标记接口,就是说该接口内是没有任何方法的。所有事件监听器都需要实现该接口。事件监听器注册在事件源上,当事件源的属性或状态改变的时候,调用相应监听器内的回调方法。
事件源不需要实现或继承任何接口或类,它是事件最初发生的地方。因为事件源需要注册事件监听器,所以事件源内需要有相应的盛放事件监听器的容器。
发布订阅模式与观察者区别:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PiWOXEO7-1624686658008)(…/images/设计模式-行为型/image-20210616015537049.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T5Ff1dkf-1624686658009)(…/images/设计模式-行为型/image-20210616134739227.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IDnyHydC-1624686658012)(…/images/设计模式-行为型/image-20210616170910966.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fqKWaMUK-1624686658013)(…/images/设计模式-行为型/image-20210622232654019.png)]
把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
通常情况下,可以通过数据链表来实现职责链模式的数据结构。
职责链模式主要包含以下角色。
责任链模式的本质是解耦请求与处理,让请求在处理链中能进行传递与被处理;理解责任链模式应当理解其模式,而不是其具体实现。责任链模式的独到之处是将其节点处理者组合成了链式结构,并允许节点自身决定是否进行请求处理或转发,相当于让请求流动起来。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pjei6rdD-1624686658014)(…/images/设计模式-行为型/3-1Q116135Z11C.gif)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pvw8Vwtc-1624686658016)(…/images/设计模式-行为型/3-1Q11613592TF.gif)]
用责任链模式设计一个请假条审批模块
分析:假如规定学生请假小于或等于 2 天,班主任可以批准;小于或等于 7 天,系主任可以批准;小于或等于 10 天,院长可以批准;其他情况不予批准;这个实例适合使用职责链模式实现。
首先,定义一个领导类(Leader),它是抽象处理者,包含了一个指向下一位领导的指针 next 和一个处理假条的抽象处理方法 handleRequest(int LeaveDays);然后,定义班主任类(ClassAdviser)、系主任类(DepartmentHead)和院长类(Dean),它们是抽象处理者的子类,是具体处理者,必须根据自己的权力去实现父类的 handleRequest(int LeaveDays) 方法,如果无权处理就将假条交给下一位具体处理者,直到最后;客户类负责创建处理链,并将假条交给链头的具体处理者(班主任)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qJE5DSnA-1624686658017)(…/images/设计模式-行为型/image-20210626082631387.png)]
对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
状态模式的解决思想是:当控制一个对象状态转换的条件表达式过于复杂时,把相关“判断逻辑”提取出来,用各个不同的类进行表示,系统处于哪种情况,直接使用相应的状态类对象进行处理,这样能把原来复杂的逻辑判断简单化,消除了 if-else、switch-case 等冗余语句,代码更有层次性,并且具备良好的扩展力。
状态模式把受环境改变的对象行为包装在不同的状态对象里,其意图是让一个对象在其内部状态改变的时候,其行为也随之改变。
状态模式包含以下主要角色。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IAcgaQSZ-1624686658019)(…/images/设计模式-行为型/3-1Q11615412U55.gif)]
用“状态模式”设计一个学生成绩的状态转换程序。
分析:
本实例包含了“不及格”“中等”和“优秀” 3 种状态,当学生的分数小于 60 分时为“不及格”状态,当分数大于等于 60 分且小于 90 分时为“中等”状态,当分数大于等于 90 分时为“优秀”状态,用状态模式来实现这个程序。
设计:
首先,定义一个抽象状态类(AbstractState),其中包含了环境属性、状态名属性和当前分数属性,以及加减分方法 addScore(intx) 和检查当前状态的抽象方法 checkState()。
然后,定义“不及格”状态类 LowState、“中等”状态类 MiddleState 和“优秀”状态类 HighState,它们是具体状态类,实现 checkState() 方法,负责检査自己的状态,并根据情况转换。
最后,定义环境类(ScoreContext),其中包含了当前状态对象和加减分的方法 add(int score),客户类通过该方法来改变成绩状态。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WDQzUyRB-1624686658020)(…/images/设计模式-行为型/image-20210626085720816.png)]
不同状态下的工作情况
分析:工人处于“开心”,“伤心”,“愤怒” 三种心情状态下,表现出不同的工作状态;
设计:将不同状态的行为抽取为不同的状态类,在环境类中根据不同的状态进行工作;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gJD4ylOa-1624686658021)(…/images/设计模式-行为型/image-20210626091954810.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LsQbTm7j-1624686658023)(…/images/设计模式-行为型/image-20210626092303710.png)]
用“状态模式”设计一个多线程的状态转换程序。
分析:多线程存在 5 种状态,分别为新建状态、就绪状态、运行状态、阻塞状态和死亡状态,各个状态当遇到相关方法调用或事件触发时会转换到其他状态,其状态转换规律如图 所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yXgOgx3Y-1624686658024)(…/images/设计模式-行为型/3-1Q116154332451.gif)]
设计:
现在先定义一个抽象状态类(TheadState),然后为图 3 所示的每个状态设计一个具体状态类,它们是新建状态(New)、就绪状态(Runnable )、运行状态(Running)、阻塞状态(Blocked)和死亡状态(Dead),每个状态中有触发它们转变状态的方法,环境类(ThreadContext)中先生成一个初始状态(New),并提供相关触发方法,下图 所示是线程状态转换程序的结构图。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h9BDRJ0P-1624686658025)(…/images/设计模式-行为型/3-1Q116154432352.gif)]
状态模式和策略模式的 UML 类图架构几乎完全一样,但两者的应用场景是不一样的。策略模式的多种算法行为择其一都能满足,彼此之间是独立的,用户可自行更换策略算法,而状态模式的各个状态间存在相互关系,彼此之间在一定条件下存在自动切换状态的效果,并且用户无法指定状态,只能设置初始状态。
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
策略模式的主要优点如下。
其主要缺点如下。
策略模式是准备一组算法,并将这组算法封装到一系列的策略类里面,作为一个抽象策略类的子类。策略模式的重心不是如何实现算法,而是如何组织这些算法,从而让程序结构更加灵活,具有更好的维护性和扩展性,现在我们来分析其基本结构和实现方法。
策略模式的主要角色如下。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OKhXBPKT-1624686658030)(…/images/设计模式-行为型/3-1Q116103K1205.gif)]
JDK中的应用:
线程池的拒绝策略设计利用了设计模式实现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EAsypBN8-1624686658031)(…/images/设计模式-行为型/RejectedExecutionHandler.png)]
定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。
中介者模式实现的关键是找出“中介者”,下面对它的结构和实现进行分析。
中介者模式包含以下主要角色。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TAuUf1bq-1624686658033)(…/images/设计模式-行为型/3-1Q1161I532V0.gif)]
提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。(具体同事之间得交互是通过具体中介者来完成的)
3. 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
4. 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。
[外链图片转存中…(img-TAuUf1bq-1624686658033)]
提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。