1. 模板方法模式

豆浆制作问题
编写制作豆浆的程序,说明如下:

  1. 制作豆浆的流程 选材—->添加配料—->浸泡—->放到豆浆机打碎
  2. 通过添加不同的配料,可以制作出不同口味的豆浆
  3. 选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的
  4. 使用 模板方法模式完成

    1.1 模板方法模式基本介绍

  5. 模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。

  6. 简单说,模板方法模式 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤。
  7. 这种类型的设计模式属于行为型模式。

    1.2 模板方法模式的原理类图

    image.png
    对原理类图的说明-即(模板方法模式的角色及职责)

  8. AbstractClass 抽象类, 类中实现了模板方法(template),定义了算法的骨架,具体子类需要去实现 其它的抽象方法operationr2,3,4

  9. ConcreteClass 实现抽象方法operationr2,3,4, 以完成算法中特点子类的步骤

    1.3 模板方法模式解决豆浆制作问题

    Client.javaPeanutSoyaMilk.javaRedBeanSoyaMilk.javaSoyaMilk.java

    1.4 模板方法模式的钩子方法

    在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”。
    PureSoyaMilk.javaRedBeanSoyaMilk.javaSoyaMilk.javaClient.javaPeanutSoyaMilk.java

    1.5 模板方法模式的注意事项和细节

  10. 基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改

  11. 实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。
  12. 既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现。
  13. 该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大
  14. 一般模板方法都加上final关键字, 防止子类重写模板方法.
  15. 模板方法模式使用场景:当要完成在某个过程,该过程要执行一系列步骤 ,这一系列的步骤基本相同,但其个别步骤在实现时 可能不同,通常考虑用模板方法模式来处理。

    2. 命令模式

    智能生活项目需求

  16. 我们买了一套智能家电,有照明灯、风扇、冰箱、洗衣机,我们只要在手机上安装app就可以控制对这些家电工作。

  17. 这些智能家电来自不同的厂家,我们不想针对每一种家电都安装一个App,分别控制,我们希望只要一个app就可以控制全部智能家电。
  18. 要实现一个app控制所有智能家电的需要,则每个智能家电厂家都要提供一个统一的接口给app调用,这时 就可以考虑使用命令模式。
  19. 命令模式可将“动作的请求者”从“动作的执行者”对象中解耦出来.
  20. 动作的请求者是手机app,动作的执行者是每个厂商的一个家电产品

    1.1 命令模式基本介绍

  21. 命令模式(Command Pattern):在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计

  22. 命名模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。
  23. 在命名模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作。
  24. 通俗易懂的理解:将军发布命令,士兵去执行。其中有几个角色: 将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)。 Invoker是调用者(将军),Receiver是被调用者(士兵),MyCommand是命令,实现了Command接口,持有接收对象

    1.2 命令模式的原理类图

    image.png
    对原理类图的说明-即(命名模式的角色及职责)

  25. Invoker 是调用者角色

  26. Command: 是命令角色,需要执行的所有命令都在这里,可以是接口或抽象类
  27. Receiver: 接收者角色,知道如何实施和执行一个请求相关的操作
  28. ConcreteCommand: 将一个接收者对象与一个动作绑定,调用接收者相应的操作,实现execute

    1.3 命令模式解决智能生活项目

    image.png
    Client.javaCommand.javaLightOffCommand.javaLightOnCommand.javaLightReceiver.javaNoCommand.javaRemoteController.javaTVOffCommand.javaTVOnCommand.javaTVReceiver.java

    1.4 命令模式的注意事项和细节

  29. 将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:”请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用。

  30. 容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令
  31. 容易实现对请求的撤销和重做
  32. 命令模式不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在在使用的时候要注意
  33. 空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦。
  34. 命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟CMD(DOS命令)订单的撤销/恢复、触发-反馈机制

    3. 访问者模式

    3.1 访问者模式基本介绍

  35. 访问者模式(Visitor Pattern),封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。

  36. 主要将数据结构与数据操作分离,解决 数据结构和操作耦合性问题
  37. 访问者模式的基本工作原理是:在被访问的类里面加一个对外提供接待访问者的接口
  38. 访问者模式主要应用场景是:需要对一个对象结构中的对象进行很多不同操作(这些操作彼此没有关联),同时需要避免让这些操作”污染”这些对象的类,可以选用访问者模式解决

    3.2 访问者模式的原理类图

    image.png
    对原理类图的说明- 即(访问者模式的角色及职责)

  39. Visitor 是抽象访问者,为该对象结构中的ConcreteElement的每一个类声明一个visit操作

  40. ConcreteVisitor :是一个具体的访问者 实现每个有Visitor 声明的操作,是每个操作实现的部分.
  41. ObjectStructure 能枚举它的元素, 可以提供一个高层的接口,用来允许访问者访问元素
  42. Element 定义一个accept 方法,接收一个访问者对象
  43. ConcreteElement 为具体元素,实现了accept 方法

代码实现
Success.javaWait.javaWoman.javaAction.javaClient.javaFail.javaMan.javaObjectStructure.javaPerson.java
应用案例的小结

  1. 上面提到了双分派,所谓双分派是指不管类怎么变化,我们都能找到期望的方法运行。双分派意味着得到执行的操作取决于请求的种类和两个接收者的类型
  2. 以上述实例为例,假设我们要添加一个Wait的状态类,考察Man类和Woman类的反应,由于使用了双分派,只需增加一个Action子类即可在客户端调用即可,不需要改动任何其他类的代码。

    3.3 访问者模式的注意事项和细节

    优点

  3. 访问者模式符合单一职责原则、让程序具有优秀的扩展性、灵活性非常高

  4. 访问者模式可以对功能进行统一,可以做报表、UI、拦截器与过滤器,适用于数据结构相对稳定的系统

缺点

  1. 具体元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,这是迪米特法则所不建议的, 这样造成了具体元素变更比较困难
  2. 违背了依赖倒转原则。访问者依赖的是具体元素,而不是抽象元素
  3. 因此,如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问者模式就是比较合适的

    4. 迭代器模式

    编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。如图:
    image.png
    传统的设计方案(类图)
    image.png

    4.1 迭代器模式基本介绍

  4. 迭代器模式(Iterator Pattern)是常用的设计模式,属于行为型模式

  5. 如果我们的集合元素是用不同的方式实现的,有数组,还有java的集合类, 或者还有其他方式,当客户端要遍历这些集合元素的时候就要使用多种遍历方式,而且还会暴露元素的内部结构,可以考虑使用迭代器模式解决。
  6. 迭代器模式,提供一种遍历集合元素的统一接口,用一致的方法遍历集合元素, 不需要知道集合对象的底层表示,即:不暴露其内部的结构。

    4.2 迭代器模式的原理类图

    image.png
    对原理类图的说明-即(迭代器模式的角色及职责)

  7. Iterator : 迭代器接口,是系统提供,含有 hasNext, next, remove

  8. ConcreteIterator : 具体的迭代器类,管理迭代
  9. Aggregate :一个统一的聚合接口, 将客户端和具体聚合解耦
  10. ConcreteAgreegate: 具体的聚合持有对象集合,并提供一个方法,返回一个迭代器,该迭代器可以正确遍历集合
  11. client: 客户端,通过iterator 和 Aggregate 依赖子类

代码实现:
Client.javaCollege.javaComputerCollege.javaComputerCollegeIterator.javaDepartment.javaInfoColleageIterator.javaInfoCollege.javaOutPutImpl.java

4.3 迭代器模式的注意事项和细节

优点

  1. 提供一个统一的方法遍历对象,客户不用再考虑聚合的类型,使用一种方法就可以遍历对象了。
  2. 隐藏了聚合的内部结构,客户端要遍历聚合的时候只能取到迭代器,而不会知道聚合的具体组成。
  3. 提供了一种设计思想,就是一个类应该只有一个引起变化的原因(叫做单一责任原则)。在聚合类中,我们把迭代器分开,就是要把管理对象集合和遍历对象集合的责任分开,这样一来集合改变的话,只影响到聚合对象。而如果遍历方式改变的话,只影响到了迭代器。
  4. 当要展示一组相似对象,或者遍历一组相同对象时使用, 适合使用迭代器模式

缺点
每个聚合对象都要一个迭代器,会生成多个迭代器不好管理类

5. 观察者模式

天气预报项目需求

  1. 气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方)。
  2. 需要设计开放型API,便于其他第三方也能接入气象站获取数据。
  3. 提供温度、气压和湿度的接口
  4. 测量数据更新时,要能实时的通知给第三方

    5.1 天气预报设计方案1-普通方案

    WeatherData类
    通过对气象站项目的分析,可以初步设计出一个WeatherData类
    image.png
    说明:

  5. 通过getXxx方法,可以让第三方接入,并得到相关信息.

  6. 当数据有更新时,气象站通过调用dataChange() 去更新数据,当第三方再次获取时,就能得到最新数据,当然也可以推送。

image.png
CurrentConditions(当前的天气情况)
可以理解成是气象局的网站 //推送
普通方案代码实现
Client.javaCurrentConditions.javaWeatherData.java
问题分析

  1. 其他第三方接入气象站获取数据的问题
  2. 无法在运行时动态的添加第三方 (新浪网站)
  3. 违反ocp原则=>观察者模式
  4. 在WeatherData中,当增加一个第三方,都需要创建一个对应的第三方的公告板对象,并加入到dataChange, 不利于维护,也不是动态加入

    1. public void dataChange() {
    2. // 将最新的信息推送给接入方
    3. currentConditions.update(getTemperature(), getPressure(), getHumidity());
    4. }

    5.2 观察者模式原理

    观察者模式类似订牛奶业务

  5. 奶站/气象局:Subject

  6. 用户/第三方网站:Observer

Subject:登记注册、移除和通知

  1. registerObserver 注册
  2. removeObserver 移除
  3. notifyObservers() 通知所有的注册的用户,根据不同需求,可以是更新数据,让用 户来取,也可能是实施推送,看具体需求定

image.png
Observer:接收输入
观察者模式:对象之间多对一依赖的一种设计方案,被依赖的对象为Subject,依赖的对象为Observer,Subject通知Observer变化,比如这里的奶站是Subject,是1的一方。用户时Observer,是多的一方。

5.3 思路分析图解(类图)

image.png
代码实现:
BaiduSite.javaClient.javaCurrentConditions.javaObserver.javaSubject.javaWeatherData.java

5.4 观察者模式的好处

  1. 观察者模式设计后,会以集合的方式来管理用户(Observer),包括注册,移除和通知。
  2. 这样,我们增加观察者(这里可以理解成一个新的公告板),就不需要去修改核心类WeatherData不会修改代码,遵守了ocp原则。

    6. 中介者模式

    智能家庭管理问题

  3. 智能家庭包括各种设备,闹钟、咖啡机、电视机、窗帘 等

  4. 主人要看电视时,各个设备可以协同工作,自动完成看电视的准备工作,比如流程为:闹铃响起->咖啡机开始做咖啡->窗帘自动落下->电视机开始播放

传统方案解决智能家庭管理问题
image.png
传统的方式的问题分析

  1. 当各电器对象有多种状态改变时,相互之间的调用关系会比较复杂
  2. 各个电器对象彼此联系,你中有我,我中有你,不利于松耦合.
  3. 各个电器对象之间所传递的消息(参数),容易混乱
  4. 当系统增加一个新的电器对象时,或者执行流程改变时,代码的可维护性、扩展性都不理想 考虑中介者模式

    6.1 中介者模式基本介绍

  5. 中介者模式(Mediator Pattern),用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互

  6. 中介者模式属于行为型模式,使代码易于维护
  7. 比如MVC模式,C(Controller控制器)是M(Model模型)和V(View视图)的中介者,在前后端交互时起到了中间人的作用

    6.2 中介者模式原理类图

    image.png
    image.png
    代码实现
    Alarm.javaClientTest.javaCoffeeMachine.javaColleague.javaConcreteMediator.javaCurtains.javaMediator.javaTV.java

    6.3 中介者模式的注意事项和细节

  8. 多个类相互耦合,会形成网状结构, 使用中介者模式将网状结构分离为星型结构,

进行解耦

  1. 减少类间依赖,降低了耦合,符合迪米特原则
  2. 中介者承担了较多的责任,一旦中介者出现了问题,整个系统就会受到影响
  3. 如果设计不当,中介者对象本身变得过于复杂,这点在实际使用时,要特别注意

    7. 备忘录模式

    游戏角色状态恢复问题
    游戏角色有攻击力和防御力,在大战Boss前保存自身的状态(攻击力和防御力),当大战Boss后攻击力和防御力下降,从备忘录对象恢复到大战前的状态
    传统的设计方案(类图)
    image.png
    传统的方式的问题分析

  4. 一个对象,就对应一个保存对象状态的对象, 这样当我们游戏的对象很多时,不利于管理,开销也很大.

  5. 传统的方式是简单地做备份,new出另外一个对象出来,再把需要备份的数据放到这个新对象,但这就暴露了对象内部的细节
  6. 解决方案: => 备忘录模式

    7.1 备忘录模式基本介绍

  7. 备忘录模式(Memento Pattern)在不破坏封装性的前提下,捕获一个对象的内 部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态

  8. 可以这里理解备忘录模式:现实生活中的备忘录是用来记录某些要去做的事情,或者是记录已经达成的共同意见的事情,以防忘记了。而在软件层面,备忘录模式有着相同的含义,备忘录对象主要用来记录一个对象的某种状态,或者某些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作
  9. 备忘录模式属于行为型模式

    7.2 备忘录模式的原理类图

    image.png
    对原理类图的说明-即(备忘录模式的角色及职责)

  10. originator : 对象(需要保存状态的对象)

  11. Memento : 备忘录对象,负责保存好记录,即Originator内部状态
  12. Caretaker: 守护者对象,负责保存多个备忘录对象, 使用集合管理,提高效率
  13. 说明:如果希望保存多个originator对象的不同时间的状态,也可以,只需要 HashMap

Caretaker.javaClient.javaMemento.javaOriginator.java
游戏角色恢复状态实例
原理类图
image.png
代码实现
Caretaker.javaClient.javaGameRole.javaMemento.java

7.3 备忘录模式的注意事项和细节

  1. 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态
  2. 实现了信息的封装,使得用户不需要关心状态的保存细节
  3. 如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存, 这个需要注意
  4. 适用的应用场景:1、后悔药。 2、打游戏时的存档。 3、Windows 里的 ctrl+ z。
  5. IE 中的后退。 4、数据库的事务管理
  6. 为了节约内存,备忘录模式可以和原型模式配合使用

    8. 解释器模式

    四则运算问题
    通过解释器模式来实现四则运算,如计算a+b-c的值,具体要求

  7. 先输入表达式的形式,比如 a+b+c-d+e, 要求表达式的字母不能重复

  8. 在分别输入a ,b, c, d, e 的值
  9. 最后求出结果:如图

image.png
传统方案解决四则运算问题分析

  1. 编写一个方法,接收表达式的形式,然后根据用户输入的数值进行解析,得到结果
  2. 问题分析:如果加入新的运算符,比如 * / ( 等等,不利于扩展,另外让一个方法来解析会造成程序结构混乱,不够清晰.
  3. 解决方案:可以考虑使用解释器模式, 即: 表达式 -> 解释器(可以有多种) -> 结果

    8.1 解释器模式基本介绍

  4. 在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。这里的词法分析器和语法分析器都可以看做是解释器

  5. 解释器模式(Interpreter Pattern):是指给定一个语言(表达式),定义它的文法的一种表示,并定义一个解释器,使用该解释器来解释语言中的句子(表达式)
  6. 应用场景
  • 应用可以将一个需要解释执行的语言中的句子表示为一个抽象语法树
  • 一些重复出现的问题可以用一种简单的语言来表达
  • 一个简单语法需要解释的场景
  1. 这样的例子还有,比如编译器、运算表达式计算、正则表达式、机器人等

    8.2

    对原理类图的说明-即(解释器模式的角色及职责)

  2. Context: 是环境角色,含有解释器之外的全局信息.

  3. AbstractExpression: 抽象表达式, 声明一个抽象的解释操作,这个方法为抽象语法树中所有的节点所共享
  4. TerminalExpression: 为终结符表达式, 实现与文法中的终结符相关的解释操作
  5. NonTermialExpression: 为非终结符表达式,为文法中的非终结符实现解释操作.
  6. 说明: 输入Context 和 TerminalExpression 信息通过Client 输入即可

image.png
image.png
代码实现
AddExpression.javaCalculator.javaClientTest.javaExpression.javaSubExpression.javaSymbolExpression.javaVarExpression.java

8.3 解释器模式的注意事项和细节

  1. 当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程序具有良好的扩展性
  2. 应用场景:编译器、运算表达式计算、正则表达式、机器人等
  3. 使用解释器可能带来的问题:解释器模式会引起类膨胀、解释器模式采用递归调用方法,将会导致调试非常复杂、效率可能降低.

    9. 状态模式

    请编写程序完成APP抽奖活动 具体要求如下:

  4. 假如每参加一次这个活动要扣除用户50积分,中奖概率是10%

  5. 奖品数量固定,抽完就不能抽奖
  6. 活动有四个状态: 可以抽奖、不能抽奖、发放奖品和奖品领完
  7. 活动的四个状态转换关系图

image.png

9.1 状态模式基本介绍

  1. 状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换
  2. 当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类

    9.2 状态模式的原理类图

    image.png
    对原理类图的说明-即(状态模式的角色及职责)

  3. Context 类为环境角色, 用于维护State实例,这个实例定义当前状态

  4. State 是抽象状态角色,定义一个接口封装与Context 的一个特点接口相关行为
  5. ConcreteState 具体的状态角色,每个子类实现一个与Context 的一个状态相关行为

image.png
代码实现
CanRaffleState.javaClientTest.javaDispenseOutState.javaDispenseState.javaNoRaffleState.javaRaffleActivity.javaState.java

9.3 状态模式的注意事项和细节

  1. 代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中
  2. 方便维护。将容易产生问题的if-else语句删除了,如果把每个状态的行为都放到一个类中,每次调用方法时都要判断当前是什么状态,不但会产出很多if-else语句,而且容易出错
  3. 符合“开闭原则”。容易增删状态
  4. 会产生很多类。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度
  5. 应用场景:当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状态要求有不同的行为的时候,可以考虑使用状态模式

    10. 策略模式

    鸭子问题

  6. 有各种鸭子(比如 野鸭、北京鸭、水鸭等, 鸭子有各种行为,比如 叫、飞行等)

  7. 显示鸭子的信息

传统方案解决鸭子问题的分析和代码实现
image.png
传统的方式实现的问题分析和解决方案

  1. 其它鸭子,都继承了Duck类,所以fly让所有子类都会飞了,这是不正确的
  2. 上面说的1 的问题,其实是继承带来的问题:对类的局部改动,尤其超类的局部改动,会影响其他部分。会有溢出效应
  3. 为了改进1问题,我们可以通过覆盖fly 方法来解决 => 覆盖解决
  4. 问题又来了,如果我们有一个玩具鸭子ToyDuck, 这样就需要ToyDuck去覆盖Duck的所有实现的方法 => 解决思路 策略模式 (strategy pattern)

    10.1 策略模式基本介绍

  5. 策略模式(Strategy Pattern)中,定义算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户

  6. 这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是具体类(定义了策略接口);第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)。

    10.2 策略模式的原理类图

    image.png
    说明:从上图可以看到,客户context 有成员变量strategy或者其他的策略接口 ,至于需要使用到哪个策略,我们可以在构造器中指定
    代码实现
    BadFlyBehavior.javaClient.javaDuck.javaFlyBehavior.javaGoodFlyBehavior.javaNoFlyBehavior.javaPekingDuck.javaQuackBehavior.javaToyDuck.javaWildDuck.java

    10.3 策略模式的注意事项和细节

  7. 策略模式的关键是:分析项目中变化部分与不变部分

  8. 策略模式的核心思想是:多用组合/聚合 少用继承;用行为类组合,而不是行为的继承。更有弹性
  9. 体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为)即可,避免了使用多重转移语句(if..else if..else)
  10. 提供了可以替换继承关系的办法: 策略模式将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展
  11. 需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞大

    11. 职责链模式

    OA系统采购审批需求

  12. 采购员采购教学器材

  13. 如果金额 小于等于5000, 由教学主任审批 (0<=x<=5000)
  14. 如果金额 小于等于10000, 由院长审批 (5000<x<=10000)
  15. 如果金额 小于等于30000, 由副校长审批 (10000<x<=30000)
  16. 如果金额 超过30000以上,有校长审批 ( 30000<x)

传统方案解决OA系统审批,传统的设计方案(类图)
image.png
传统方案解决OA系统审批问题分析

  1. 传统方式是:接收到一个采购请求后,根据采购金额来调用对应的Approver (审批人)完成审批。
  2. 传统方式的问题分析 : 客户端这里会使用到 分支判断(比如 switch) 来对不同的采购请求处理, 这样就存在如下问题 (1) 如果各个级别的人员审批金额发生变化,在客户端的也需要变化 (2) 客户端必须明确的知道 有多少个审批级别和访问
  3. 这样 对一个采购请求进行处理 和 Approver (审批人) 就存在强耦合关系,不利于代码的扩展和维护
  4. 解决方案 =》 职责链模式

    11.1 职责链模式基本介绍

  5. 职责链模式(Chain of Responsibility Pattern),又叫 责任链模式,为请求创建了一个接收者对象的链(简单示意图)。这种模式对请求的发送者和接收者进行解耦。

  6. 职责链模式通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。

image.png

11.2 职责链模式的原理类图

image.png
职责链模式(Chain Of Responsibility),使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止.
对原理类图的说明-即(职责链模式的角色及职责)

  1. Handler : 抽象的处理者, 定义了一个处理请求的接口, 同时含义另外Handler
  2. ConcreteHandlerA , B 是具体的处理者, 处理它自己负责的请求, 可以访问它的后继者(即下一个处理者), 如果可以处理当前请求,则处理,否则就将该请求交给后继者去处理,从而形成一个职责链
  3. Request , 含义很多属性,表示一个请求

image.png
代码实现
Approver.javaClient.javaCollegeApprover.javaDepartmentApprover.javaPurchaseRequest.javaSchoolMasterApprover.javaViceSchoolMasterApprover.java

11.3 职责链模式的注意事项和细节

  1. 将请求和处理分开,实现解耦,提高系统的灵活性
  2. 简化了对象,使对象不需要知道链的结构
  3. 性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般通过在Handler中设置一个最大节点数量,在setNext()方法中判断是否已经超过阀值,超过则不允许该链建立,避免出现超长链无意识地破坏系统性能
  4. 调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂
  5. 最佳应用场景:有多个对象可以处理同一个请求时,比如:多级请求、请假/加薪等审批流程、Java Web中Tomcat对Encoding的处理、拦截器