本章目录标题
第24章 状态模式
24.1 应用场景:APP抽奖
请编写程序完成 APP 抽奖活动 具体要求如下:
(1)假如每参加一次这个活动要扣除用户 50 积分,中奖概率是 10%
(2)奖品数量固定,抽完就不能抽奖
(3)活动有四个状态: 可以抽奖、不能抽奖、发放奖品和奖品领完 4) 活动的四个状态转换关系图
24.2 状态模式
- 基本介绍
(1)状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换
(2)当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类 - 原理类图
状态模式的角色及职责)
(1)Context 类为环境角色, 用于维护 State 实例,这个实例定义当前状态
(2)State 是抽象状态角色,定义一个接口封装与 Context 的一个特点接口相关行为
(3)ConcreteState 具体的状态角色,每个子类实现一个与 Context 的一个状态相关行为。
24.3 应用案例:APP抽奖
- 类图
(1)定义出一个接口叫状态接口,每个状态都实现它。
(2)接口有扣除积分方法、抽奖方法、发放奖品方法
说明
(1)状态类:NoRaffleState(不可抽奖)、CanRaffleState(可以抽奖)、DispensePrizeState(领奖状态)、DispenseOutPrizeState(奖品领完状态)。
2. 代码实现
(1)状态抽象类
/**
* 状态抽象类
*/
public abstract class State {
// 扣除积分 - 50
public abstract void deductMoney();
// 是否抽中奖品
public abstract boolean raffle();
// 发放奖品
public abstract void dispensePrize();
}
(2)具体状态栏
不能抽奖状态
public class NoRaffleState extends State {
// 初始化时传入活动引用,扣除积分后改变其状态
RaffleActivity activity;
public NoRaffleState(RaffleActivity activity) {
this.activity = activity;
}
// 当前状态可以扣积分 , 扣除后,将状态设置成可以抽奖状态
@Override
public void deductMoney() {
System.out.println("扣除50积分成功,您可以抽奖了");
activity.setState(activity.getCanRaffleState());
}
// 当前状态不能抽奖
@Override
public boolean raffle() {
System.out.println("扣了积分才能抽奖喔!");
return false;
}
// 当前状态不能发奖品
@Override
public void dispensePrize() {
System.out.println("不能发放奖品");
}
}
可以抽奖状态
public class CanRaffleState extends State {
RaffleActivity activity;
public CanRaffleState(RaffleActivity activity) {
this.activity = activity;
}
//已经扣除了积分,不能再扣
@Override
public void deductMoney() {
System.out.println("已经扣取过了积分");
}
//可以抽奖, 抽完奖后,根据实际情况,改成新的状态
@Override
public boolean raffle() {
System.out.println("正在抽奖,请稍等!");
Random r = new Random();
int num = r.nextInt(10);
// 10%中奖机会
if(num == 0){
// 改变活动状态为发放奖品 context
activity.setState(activity.getDispenseState());
return true;
}else{
System.out.println("很遗憾没有抽中奖品!");
// 改变状态为不能抽奖
activity.setState(activity.getNoRafflleState());
return false;
}
}
// 不能发放奖品
@Override
public void dispensePrize() {
System.out.println("没中奖,不能发放奖品");
}
}
发放奖品状态
public class DispenseState extends State {
// 初始化时传入活动引用,发放奖品后改变其状态
RaffleActivity activity;
public DispenseState(RaffleActivity activity) {
this.activity = activity;
}
@Override
public void deductMoney() {
System.out.println("不能扣除积分");
}
@Override
public boolean raffle() {
System.out.println("不能抽奖");
return false;
}
//发放奖品
@Override
public void dispensePrize() {
if(activity.getCount() > 0){
System.out.println("恭喜中奖了");
// 改变状态为不能抽奖
activity.setState(activity.getNoRafflleState());
}else{
System.out.println("很遗憾,奖品发送完了");
// 改变状态为奖品发送完毕, 后面我们就不可以抽奖
activity.setState(activity.getDispensOutState());
//System.out.println("抽奖活动结束");
//System.exit(0);
}
}
}
奖品发放完毕状态
public class DispenseOutState extends State {
// 初始化时传入活动引用
RaffleActivity activity;
public DispenseOutState(RaffleActivity activity) {
this.activity = activity;
}
@Override
public void deductMoney() {
System.out.println("奖品发送完了,请下次再参加");
}
@Override
public boolean raffle() {
System.out.println("奖品发送完了,请下次再参加");
return false;
}
@Override
public void dispensePrize() {
System.out.println("奖品发送完了,请下次再参加");
}
}
(3)抽奖活动
public class RaffleActivity {
// state 表示活动当前的状态,是变化
State state = null;
// 奖品数量
int count = 0;
// 四个属性,表示四种状态
State noRafflleState = new NoRaffleState(this);
State canRaffleState = new CanRaffleState(this);
State dispenseState = new DispenseState(this);
State dispensOutState = new DispenseOutState(this);
//构造器
//1. 初始化当前的状态为 noRafflleState(即不能抽奖的状态)
//2. 初始化奖品的数量
public RaffleActivity( int count) {
this.state = getNoRafflleState();
this.count = count;
}
//扣分, 调用当前状态的 deductMoney
public void debuctMoney(){
state.deductMoney();
}
//抽奖
public void raffle(){
// 如果当前的状态是抽奖成功
if(state.raffle()){
//领取奖品
state.dispensePrize();
}
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
//这里请大家注意,每领取一次奖品,count--
public int getCount() {
int curCount = count;
count--;
return curCount;
}
public void setCount(int count) {
this.count = count;
}
//State、NoRafflleState、CanRaffleState、DispenseState、DispensOutState的set和get方法
}
24.4 状态模式的注意事项
(1)代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中
(2)方便维护。将容易产生问题的 if-else 语句删除了,如果把每个状态的行为都放到一个类中,每次调用方法时都
要判断当前是什么状态,不但会产出很多 if-else 语句,而且容易出错
(3)符合开闭原则。容易增删状态
(4)会产生很多类。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度
(5)应用场景:当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状态要求有不同的行为的时候,可以考虑使用状态模式
第25章 策略模式
25.1 应用场景:鸭子项目
(1)有各种鸭子(比如 野鸭、北京鸭、水鸭等, 鸭子有各种行为,比如 叫、飞行等)
(2)显示鸭子的信息
25.2 传统方案
- 传统方案
写一个Duck类,鸭子继承Duck类,如果没有新的内容,就继承;如果有新的内容就添加, - 传统方案分析
(1)其它鸭子,都继承了 Duck 类,所以 fly 让所有子类都会飞了,这是不正确的
(2)上面说的 1 的问题,其实是继承带来的问题:对类的局部改动,尤其超类的局部改动,会影响其他部分。会有溢出效应
(3)为了改进 1 问题,我们可以通过覆盖 fly 方法来解决 => 覆盖解决
(4)问题又来了,如果我们有一个玩具鸭子 ToyDuck, 这样就需要 ToyDuck 去覆盖 Duck 的所有实现的方法 - 解 决思路 -》 策略模式 (strategy pattern)
25.3 策略模式
- 基本介绍
(1)策略模式(Strategy Pattern)中,定义算法族(策略组),分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
(2)这算法体现了几个设计原则,
第一、把变化的代码从不变的代码中分离出来;
第二、针对接口编程而不是具体类(定义了策略接口);
第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)。 - 类图
说明:
从上图可以看到,客户 context 有成员变量 strategy 或者其他的策略接口
,至于需要使用到哪个策略,我们可以在构造器中指定
25.4 应用案例:鸭子项目
- 策略模式
策略模式:分别封装行为接口,实现算法族,超类里放行为接口对象,在子类里具体设定行为对象。原则就是: 分离变化部分,封装接口,基于接口编程各种功能。此模式让行为的变化独立于算法的使用者 - 类图
- 代码实现
(1)飞翔接口
public interface FlyBehavior {
void fly(); // 子类具体实现
}
(2)具体飞翔实现类
public class GoodFlyBehavior implements FlyBehavior {
@Override
public void fly() {
System.out.println(" 飞翔技术高超 ~~~");
}
}
NoFlyBehavior、BadFlyBehavior类似。
(3)聚合各类行为的Duck类
public abstract class Duck {
//属性, 策略接口
FlyBehavior flyBehavior;
//其它属性<->策略接口
QuackBehavior quackBehavior;
public Duck() {
}
public abstract void display();//显示鸭子信息
public void quack() {
System.out.println("鸭子嘎嘎叫~~");
}
public void swim() {
System.out.println("鸭子会游泳~~");
}
public void fly() {
//改进
if(flyBehavior != null) {
flyBehavior.fly();
}
}
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
}
(4)鸭子实现类
public class PekingDuck extends Duck {
//假如北京鸭可以飞翔,但是飞翔技术一般
public PekingDuck() {
flyBehavior = new BadFlyBehavior();
}
@Override
public void display() {
System.out.println("~~北京鸭~~~");
}
}
(5)客户端
public class Client {
public static void main(String[] args) {
PekingDuck pekingDuck = new PekingDuck();
pekingDuck.fly();
//动态改变某个对象的行为, 北京鸭 不能飞
pekingDuck.setFlyBehavior(new NoFlyBehavior());
System.out.println("北京鸭的实际飞翔能力");
pekingDuck.fly();
}
}
25,5 策略模式的注意事项
(1)策略模式的关键是:分析项目中变化部分与不变部分
(2)策略模式的核心思想是:多用组合/聚合 少用继承;用行为类组合,而不是行为的继承。更有弹性
(3)体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为)即可,避免了使用多重转移语句(if…else if…else)
(4)提供了可以替换继承关系的办法: 策略模式将算法封装在独立的 Strategy 类中使得你可以独立于其 Context 改变它,使它易于切换、易于理解、易于扩展
(5)需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞大
第26章 职责链模式
26.1 应用场景:学校 OA 系统的采购审批项目
需求是:采购员采购教学器材:
(1)如果金额 小于等于 5000, 由教学主任审批 (0<=x<=5000)
(2)如果金额 小于等于 10000, 由院长审批 (5000<x<=10000)
(3)如果金额 小于等于 30000, 由副校长审批 (10000<x<=30000)
(4)如果金额 超过 30000 以上,有校长审批 ( 30000<x)
26.2 传统方案
- 传统方案
- 传统方案问题分析
(1)传统方式是:接收到一个采购请求后,根据采购金额来调用对应的 Approver (审批人)完成审批。
(2)传统方式的问题分析 : 客户端这里会使用到 分支判断(比如 switch) 来对不同的采购请求处理, 这样就存在如下问题 (1) 如果各个级别的人员审批金额发生变化,在客户端的也需要变化 (2) 客户端必须明确的知道 有多少个审批级别和访问
(3)这样 对一个采购请求进行处理 和 Approver (审批人) 就存在强耦合关系,不利于代码的扩展和维护 - 解决方案:职责链模式
26.3 职责链模式(责任链模式)
- 基本介绍
(1)职责链模式(Chain of Responsibility Pattern),为请求创建了一个接收者对象的链(简单示意图)。这种模式对请求的发送者和接收者进行解耦。
(2)职责链模式通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
(3)这种类型的设计模式属于行为型模式
2. 类图
职责链模式的角色
(1)Handler : 抽象的处理者, 定义了一个处理请求的接口, 同时含义另外 Handler
(2)ConcreteHandlerA , B 是具体的处理者,处理它自己负责的请求, 可以访问它的后继者(即下一个处理者), 如果可以处理当前请求,则处理,否则就将该请求交个 后继者去处理,从而形成一个职责链
(3)Request , 含义很多属性,表示一个请求
26.4 OA系统采购审批
- 类图
- 代码实现
(1)请求类
//请求类
public class PurchaseRequest {
private int type = 0; //请求类型
private float price = 0.0f; //请求金额
private int id = 0;
//构造器
public PurchaseRequest(int type, float price, int id) {
this.type = type;
this.price = price;
this.id = id;
}
public int getType() {
return type;
}
public float getPrice() {
return price;
}
public int getId() {
return id;
}
}
(2)抽象处理类
public abstract class Approver {
Approver approver; //下一个处理者
String name; // 名字
public Approver(String name) {
this.name = name;
}
//下一个处理者
public void setApprover(Approver approver) {
this.approver = approver;
}
//处理审批请求的方法,得到一个请求, 处理是子类完成,因此该方法做成抽象
public abstract void processRequest(PurchaseRequest purchaseRequest);
}
(3)具体实现类
public class DepartmentApprover extends Approver {
public DepartmentApprover(String name) {
super(name);
}
@Override
public void processRequest(PurchaseRequest purchaseRequest) {
if(purchaseRequest.getPrice() <= 5000) {
System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
}else {
approver.processRequest(purchaseRequest);
}
}
}
CollegeApprover、SchoolMasterApprover、ViceSchoolMasterApprover类类似。
(4)客户端
public class Client {
public static void main(String[] args) {
//创建一个请求
PurchaseRequest purchaseRequest = new PurchaseRequest(1, 31000, 1);
//创建相关的审批人
DepartmentApprover departmentApprover = new DepartmentApprover("张主任");
CollegeApprover collegeApprover = new CollegeApprover("李院长");
ViceSchoolMasterApprover viceSchoolMasterApprover = new ViceSchoolMasterApprover("王副校");
SchoolMasterApprover schoolMasterApprover = new SchoolMasterApprover("佟校长");
//需要将各个审批级别的下一个设置好 (处理人构成环形: )
departmentApprover.setApprover(collegeApprover);
collegeApprover.setApprover(viceSchoolMasterApprover);
viceSchoolMasterApprover.setApprover(schoolMasterApprover);
schoolMasterApprover.setApprover(departmentApprover);
departmentApprover.processRequest(purchaseRequest);
viceSchoolMasterApprover.processRequest(purchaseRequest);
}
}
26.5 职责链模式的注意事项
(1)将请求和处理分开,实现解耦,提高系统的灵活性
(2)简化了对象,使对象不需要知道链的结构
(3)性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般通过在 Handler 中设置一个最大节点数量,在 setNext()方法中判断是否已经超过阀值,超过则不允许该链建立,避免出现超长链无意识地破坏系统性能。
(4)调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂
(5)最佳应用场景:有多个对象可以处理同一个请求时,比如:多级请求、请假/加薪等审批流程、Java Web 中 Tomcat对 Encoding 的处理、拦截器。