1.1.【案例1】及其实现
【案例1】通过按钮来控制一个电梯的状态,一个电梯有开门状态,关门状态,停止状态,运行状态。每一种状态改变,都有可能要根据其他状态来更新处理。例如,如果电梯门现在处于运行时状态,就不能进行开门操作,而如果电梯门是停止状态,就可以执行开门操作
a.类图如下:
b.代码如下:
public interface ILift {
//电梯的4个状态
//开门状态
public final static int OPENING_STATE = 1;
//关门状态
public final static int CLOSING_STATE = 2;
//运行状态
public final static int RUNNING_STATE = 3;
//停止状态
public final static int STOPPING_STATE = 4;
//设置电梯的状态
public void setState(int state);
//电梯的动作
public void open();
public void close();
public void run();
public void stop();
}
public class Lift implements ILift {
private int state;
@Override
public void setState(int state) {
this.state = state;
}
//执行关门动作
@Override
public void close() {
switch (this.state) {
case OPENING_STATE:
System.out.println("电梯关门了。。。");//只有开门状态可以关闭电梯门,可以对应电梯状态表来看
this.setState(CLOSING_STATE);//关门之后电梯就是关闭状态了
break;
case CLOSING_STATE:
//do nothing //已经是关门状态,不能关门
break;
case RUNNING_STATE:
//do nothing //运行时电梯门是关着的,不能关门
break;
case STOPPING_STATE:
//do nothing //停止时电梯也是关着的,不能关门
break;
}
}
//执行开门动作
@Override
public void open() {
switch (this.state) {
case OPENING_STATE://门已经开了,不能再开门了
//do nothing
break;
case CLOSING_STATE://关门状态,门打开:
System.out.println("电梯门打开了。。。");
this.setState(OPENING_STATE);
break;
case RUNNING_STATE:
//do nothing 运行时电梯不能开门
break;
case STOPPING_STATE:
System.out.println("电梯门开了。。。");//电梯停了,可以开门了
this.setState(OPENING_STATE);
break;
}
}
//执行运行动作
@Override
public void run() {
switch (this.state) {
case OPENING_STATE://电梯不能开着门就走
//do nothing
break;
case CLOSING_STATE://门关了,可以运行了
System.out.println("电梯开始运行了。。。");
this.setState(RUNNING_STATE);//现在是运行状态
break;
case RUNNING_STATE:
//do nothing 已经是运行状态了
break;
case STOPPING_STATE:
System.out.println("电梯开始运行了。。。");
this.setState(RUNNING_STATE);
break;
}
}
//执行停止动作
@Override
public void stop() {
switch (this.state) {
case OPENING_STATE: //开门的电梯已经是是停止的了(正常情况下)
//do nothing
break;
case CLOSING_STATE://关门时才可以停止
System.out.println("电梯停止了。。。");
this.setState(STOPPING_STATE);
break;
case RUNNING_STATE://运行时当然可以停止了
System.out.println("电梯停止了。。。");
this.setState(STOPPING_STATE);
break;
case STOPPING_STATE:
//do nothing
break;
}
}
}
public class Client {
public static void main(String[] args) {
Lift lift = new Lift();
lift.setState(ILift.STOPPING_STATE);//电梯是停止的
lift.open();//开门
lift.close();//关门
lift.run();//运行
lift.stop();//停止
}
}
c.问题分析:
- 1.使用了
大量的switch…case这样的判断(if…else也是一样)
,使程序的可阅读性变差。 - 2.扩展性很差。如果新加了断电的状态,我们需要修改上面判断逻辑
1.2.案例2:
【案例2】抽奖活动:假如每参加一次抽奖活动要扣除用户 50 积分,中奖概率是 10%,奖品数量固定,抽完就不能抽奖,活动有
四个状态
:可以抽奖、不能抽奖、发放奖品和奖品领完,活动的四个状态转换关系图
2.状态模式
2.1.概念:
- 1.状态模式(State Pattern):它主要用来
解决对象在多种状态转换时,需要对外输出不同的行为的问题
。状态和行为是一一对应的,状态之间可以相互转换。 - 2.当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类
2.2.原理类图:
状态模式包含以下主要角色:
- 1.
State
:是抽象状态角色,定义一个接口来封装与 Context 的一个特点接口相关行为 - 2.
Context
:也称为上下文,它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理 - 3.
ConcreteState
:具体的状态角色,每个子类实现一个与 Context 的一个状态相关行为
2.3.案例改善
a.案例1的类图设计如下:
b.案例2类图设计如下:
- 1.根据抽奖状态关系转换图,可以在
抽象状态
中定义三个方法- 扣除积分(每次抽奖前都要扣除积分)
- 是否中奖(抽奖时有两种情况,中奖和不中奖)
- 领取奖品(领取奖品有两种情况,有奖品和没有奖品)
- 2.ConcreteState有四种状态:不能抽奖状态NoRaffileState,可抽奖状态、领取奖品状态、 奖品领完状态
2.4.编码实现:
a.案例2编码实现:
a1.抽象状态类
public abstract class State {
/**
* 扣除积分 -50
*/
public abstract void deductIntegral();
/**
* 是否抽中奖品
* @return
*/
public abstract boolean raffle();
/**
* 发放奖品
*/
public abstract void dispensePrize();
}
a2.四个状态类:
- 1.
不能抽奖状态
: 提前说明:RaffleActivity为抽奖活动类,里面聚合了四种抽奖状态,在执行这三个方法时会根据情况改变状态。
//不能抽奖状态
public class NoRaffleState extends State {
private RaffleActivity raffleActivity;
public NoRaffleState(RaffleActivity raffleActivity){
this.raffleActivity = raffleActivity;
}
//扣除积分 -50
@Override
public void deductIntegral() {
System.out.println("扣除50积分成功,您可以抽奖了");
//状态转换:不能抽奖状态 ===> 抽奖状态
raffleActivity.setState(raffleActivity.getRaffleState());
}
//是否抽中奖品
@Override
public boolean raffle() {
System.out.println("对不起,扣除积分才能抽奖");
return false;
}
//发放奖品
@Override
public void dispensePrize() {
System.out.println("没参与抽奖,不能发放奖品");
}
}
- 2.
抽奖状态
//抽奖状态
public class RaffleState extends State {
private RaffleActivity raffleActivity;
public RaffleState(RaffleActivity raffleActivity) {
this.raffleActivity = raffleActivity;
}
//扣除积分 -50
@Override
public void deductIntegral() {
System.out.println("已经扣过积分,参与抽奖吧");
}
//是否抽中奖品
@Override
public boolean raffle() {
System.out.println("要抽奖啦");
int i = new Random().nextInt(5);
//只有 25% 的可能性抽中奖品
if(i == 1){
//抽奖状态 ===> 发放奖品状态
raffleActivity.setState(raffleActivity.getDispenseState());
return true;
}else {
System.out.println("没抽中奖品,再接再厉");
//抽奖状态 ===> 不能抽奖状态
raffleActivity.setState(raffleActivity.getNoRaffleState());
return false;
}
}
//发放奖品
@Override
public void dispensePrize() {
System.out.println("没中奖,不给奖品");
}
}
- 3.
发放奖品状态
//发放奖品状态
public class DispenseState extends State {
private RaffleActivity raffleActivity;
public DispenseState(RaffleActivity raffleActivity) {
this.raffleActivity = raffleActivity;
}
//扣除积分 -50
@Override
public void deductIntegral() {
System.out.println("发奖品呢,扣什么积分");
}
//是否抽中奖品
@Override
public boolean raffle() {
System.out.println("你已经在领奖了,还抽奖干嘛");
return false;
}
//发放奖品
@Override
public void dispensePrize() {
int count = raffleActivity.getCount();
if(count > 0){
System.out.println("恭喜你,领取奖品成功,奖品剩余 " + --count + "个");
//领奖状态 ===> 不能抽奖状态
raffleActivity.setState(raffleActivity.getNoRaffleState());
raffleActivity.setCount(count);
}else {
System.out.println("不好意思,没有奖品了");
//领奖状态 ===> 奖品领完状态
raffleActivity.setState(raffleActivity.getDispenseOutState());
}
}
}
- 4.
奖品领完状态
//奖品领完状态
public class DispenseOutState extends State {
private RaffleActivity raffleActivity;
public DispenseOutState(RaffleActivity raffleActivity) {
this.raffleActivity = raffleActivity;
}
//扣除积分 -50
@Override
public void deductIntegral() {
System.out.println("奖品没了,就不扣您的积分了");
}
//是否抽中奖品
@Override
public boolean raffle() {
System.out.println("奖品都没了,您还抽什么奖啊!");
return false;
}
//发放奖品
@Override
public void dispensePrize() {
System.out.println("奖品都没了,不用来领奖了!");
}
}
a3.Activity抽奖活动类:
public class RaffleActivity {
//当前的抽奖状态,状态是变化的
private State state;
//奖品数量
private int count;
// 四个属性,表示四种状态
//不能抽奖状态
private NoRaffleState noRaffleState = new NoRaffleState(this);
//能抽奖状态
private RaffleState raffleState = new RaffleState(this);
//发放奖品状态
private DispenseState dispenseState = new DispenseState(this);
//奖品领完状态
private DispenseOutState dispenseOutState = new DispenseOutState(this);
public RaffleActivity(int count) {
System.out.println("快来玩啊,抽奖活动开始了");
// 初始化状态为 ===> 不能抽奖状态
this.state = this.noRaffleState;
this.count = count;
}
//扣除积分;
public void deductIntegral(){
state.deductIntegral();
}
//抽奖;
public void raffle(){
if(state.raffle()){
//抽中了就做好发奖品的准备吧
state.dispensePrize();
}
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
public NoRaffleState getNoRaffleState() {
return noRaffleState;
}
public void setNoRaffleState(NoRaffleState noRaffleState) {
this.noRaffleState = noRaffleState;
}
public RaffleState getRaffleState() {
return raffleState;
}
public void setRaffleState(RaffleState raffleState) {
this.raffleState = raffleState;
}
public DispenseState getDispenseState() {
return dispenseState;
}
public void setDispenseState(DispenseState dispenseState) {
this.dispenseState = dispenseState;
}
public DispenseOutState getDispenseOutState() {
return dispenseOutState;
}
public void setDispenseOutState(DispenseOutState dispenseOutState) {
this.dispenseOutState = dispenseOutState;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
a4.客户端调用:
- 1.编码实现客户端调用Activity抽奖活动类:
public class Client {
public static void main(String[] args) {
RaffleActivity raffleActivity = new RaffleActivity(2);
for (int i = 1; i <= 10; i++){
System.out.println("-----第" + i + "次抽奖-----");
raffleActivity.deductIntegral();
raffleActivity.raffle();
}
}
}
- 2.结果:
快来玩啊,抽奖活动开始了
-----第1次抽奖-----
扣除50积分成功,您可以抽奖了
要抽奖啦
恭喜你,领取奖品成功,奖品剩余 1个
-----第2次抽奖-----
扣除50积分成功,您可以抽奖了
要抽奖啦
没抽中奖品,再接再厉
-----第3次抽奖-----
扣除50积分成功,您可以抽奖了
要抽奖啦
恭喜你,领取奖品成功,奖品剩余 0个
-----第4次抽奖-----
扣除50积分成功,您可以抽奖了
要抽奖啦
没抽中奖品,再接再厉
-----第5次抽奖-----
扣除50积分成功,您可以抽奖了
要抽奖啦
没抽中奖品,再接再厉
-----第6次抽奖-----
扣除50积分成功,您可以抽奖了
要抽奖啦
没抽中奖品,再接再厉
-----第7次抽奖-----
扣除50积分成功,您可以抽奖了
要抽奖啦
没抽中奖品,再接再厉
-----第8次抽奖-----
扣除50积分成功,您可以抽奖了
要抽奖啦
没抽中奖品,再接再厉
-----第9次抽奖-----
扣除50积分成功,您可以抽奖了
要抽奖啦
不好意思,没有奖品了
-----第10次抽奖-----
奖品没了,就不扣您的积分了
奖品都没了,您还抽什么奖啊!
b.案例1编码实现:
b1.抽象状态类
//抽象状态类
public abstract class LiftState {
//定义一个环境角色,也就是封装状态的变化引起的功能变化
protected Context context;
public void setContext(Context context) {
this.context = context;
}
//电梯开门动作
public abstract void open();
//电梯关门动作
public abstract void close();
//电梯运行动作
public abstract void run();
//电梯停止动作
public abstract void stop();
}
//开启状态
public class OpenningState extends LiftState {
//开启当然可以关闭了,我就想测试一下电梯门开关功能
@Override
public void open() {
System.out.println("电梯门开启...");
}
@Override
public void close() {
//状态修改
super.context.setLiftState(Context.closeingState);
//动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作
super.context.getLiftState().close();
}
//电梯门不能开着就跑,这里什么也不做
@Override
public void run() {
//do nothing
}
//开门状态已经是停止的了
@Override
public void stop() {
//do nothing
}
}
//运行状态
public class RunningState extends LiftState {
//运行的时候开电梯门?你疯了!电梯不会给你开的
@Override
public void open() {
//do nothing
}
//电梯门关闭?这是肯定了
@Override
public void close() {//虽然可以关门,但这个动作不归我执行
//do nothing
}
//这是在运行状态下要实现的方法
@Override
public void run() {
System.out.println("电梯正在运行...");
}
//这个事绝对是合理的,光运行不停止还有谁敢做这个电梯?!估计只有上帝了
@Override
public void stop() {
super.context.setLiftState(Context.stoppingState);
super.context.stop();
}
}
//停止状态
public class StoppingState extends LiftState {
//停止状态,开门,那是要的!
@Override
public void open() {
//状态修改
super.context.setLiftState(Context.openningState);
//动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作
super.context.getLiftState().open();
}
@Override
public void close() {//虽然可以关门,但这个动作不归我执行
//状态修改
super.context.setLiftState(Context.closeingState);
//动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作
super.context.getLiftState().close();
}
//停止状态再跑起来,正常的很
@Override
public void run() {
//状态修改
super.context.setLiftState(Context.runningState);
//动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作
super.context.getLiftState().run();
}
//停止状态是怎么发生的呢?当然是停止方法执行了
@Override
public void stop() {
System.out.println("电梯停止了...");
}
}
//关闭状态
public class ClosingState extends LiftState {
@Override
//电梯门关闭,这是关闭状态要实现的动作
public void close() {
System.out.println("电梯门关闭...");
}
//电梯门关了再打开,逗你玩呢,那这个允许呀
@Override
public void open() {
super.context.setLiftState(Context.openningState);
super.context.open();
}
//电梯门关了就跑,这是再正常不过了
@Override
public void run() {
super.context.setLiftState(Context.runningState);
super.context.run();
}
//电梯门关着,我就不按楼层
@Override
public void stop() {
super.context.setLiftState(Context.stoppingState);
super.context.stop();
}
}
//环境角色
public class Context {
//定义出所有的电梯状态
public final static OpenningState openningState = new OpenningState();//开门状态,这时候电梯只能关闭
public final static ClosingState closeingState = new ClosingState();//关闭状态,这时候电梯可以运行、停止和开门
public final static RunningState runningState = new RunningState();//运行状态,这时候电梯只能停止
public final static StoppingState stoppingState = new StoppingState();//停止状态,这时候电梯可以开门、运行
//定义一个当前电梯状态
private LiftState liftState;
public LiftState getLiftState() {
return this.liftState;
}
public void setLiftState(LiftState liftState) {
//当前环境改变
this.liftState = liftState;
//把当前的环境通知到各个实现类中
this.liftState.setContext(this);
}
public void open() {
this.liftState.open();
}
public void close() {
this.liftState.close();
}
public void run() {
this.liftState.run();
}
public void stop() {
this.liftState.stop();
}
}
//测试类
public class Client {
public static void main(String[] args) {
Context context = new Context();
context.setLiftState(new ClosingState());
context.open();
context.close();
context.run();
context.stop();
}
}
3.状态模式的优点与应用:
2.1.优缺点:
a.优点:
- 1.代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中
- 2.方便维护。将容易产生问题的 if-else 语句删除了,如果把每个状态的行为都放到一个类中,每次调用方法时都要判断当前是什么状态,不但会产出很多 if-else 语句,而且容易出错
- 3.符合“开闭原则”。容易增删状态
- 4.会产生很多类。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度
b.缺点:
- 1.状态模式的使用必然会增加系统类和对象的个数。
- 2.状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱
- 3.状态模式对"开闭原则"的支持并不太好
2.2.应用场景:
- 当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状态要求有不同的行为的时候,可以考虑使用状态模式
- 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。
4.借贷平台源码解析:
4.1.传统方式实现分析
- 1.实现:通过if/else判断订单的状态,从而实现不同的逻辑
- 2.问题:这类代码难以应对变化,在添加一种状态时,我们需要手动添加if/else,在添加一种功能时,要对所有的状态进行判断。因此代码会变得越来越臃肿,并且一旦没有处理某个状态便会发生极其严重的BUG,难以维护
- 3.改进:借贷平台的订单,有审核-发布-抢单等等步骤,随着操作的不同,会改变订单的状态,项目中的这个模块实现就会使用到状态模式
4.2.业务实际流程:
- 2.对上述业务流程总结的状态图:
4.3.类图设计:
4.4.编码实现:
a.状态枚举类:StateEnum:
package com.atguigu.state.money;
/**
* 状态枚举类
* @author Administrator
*
*/
public enum StateEnum {
//订单生成
GENERATE(1, "GENERATE"),
//已审核
REVIEWED(2, "REVIEWED"),
//已发布
PUBLISHED(3, "PUBLISHED"),
//待付款
NOT_PAY(4, "NOT_PAY"),
//已付款
PAID(5, "PAID"),
//已完结
FEED_BACKED(6, "FEED_BACKED");
private int key;
private String value;
StateEnum(int key, String value) {
this.key = key;
this.value = value;
}
public int getKey() {return key;}
public String getValue() {return value;}
}
b.状态接口:State:
package com.atguigu.state.money;
/**
* 状态接口
* @author Administrator
*
*/
public interface State {
/**
* 电审
*/
void checkEvent(Context context);
/**
* 电审失败
*/
void checkFailEvent(Context context);
/**
* 定价发布
*/
void makePriceEvent(Context context);
/**
* 接单
*/
void acceptOrderEvent(Context context);
/**
* 无人接单失效
*/
void notPeopleAcceptEvent(Context context);
/**
* 付款
*/
void payOrderEvent(Context context);
/**
* 接单有人支付失效
*/
void orderFailureEvent(Context context);
/**
* 反馈
*/
void feedBackEvent(Context context);
String getCurrentState();
}
c.抽象状态类:
使用抽象状态类来默认实现方法之后,具体的状态类(其子类)可以只选择所需要的方法来进行重写:
package com.atguigu.state.money;
/**
* 抽象类,默认实现了 State 接口的所有方法
* 该类的所有方法,其子类(具体的状态类),可以有选择的进行重写
*/
public abstract class AbstractState implements State {
protected static final RuntimeException EXCEPTION = new RuntimeException("操作流程不允许");
@Override
public void checkEvent(Context context) {
throw EXCEPTION;
}
@Override
public void checkFailEvent(Context context) {
throw EXCEPTION;
}
@Override
public void makePriceEvent(Context context) {
throw EXCEPTION;
}
@Override
public void acceptOrderEvent(Context context) {
throw EXCEPTION;
}
@Override
public void notPeopleAcceptEvent(Context context) {
throw EXCEPTION;
}
@Override
public void payOrderEvent(Context context) {
throw EXCEPTION;
}
@Override
public void orderFailureEvent(Context context) {
throw EXCEPTION;
}
@Override
public void feedBackEvent(Context context) {
throw EXCEPTION;
}
}
d.具体状态类:
package com.atguigu.state.money;
/**
* 反馈状态
*/
class FeedBackState extends AbstractState {
@Override
public String getCurrentState() {
return StateEnum.FEED_BACKED.getValue();
}
}
/**
* 通用状态
*/
class GenerateState extends AbstractState {
@Override
public void checkEvent(Context context) {
context.setState(new ReviewState());
}
@Override
public void checkFailEvent(Context context) {
context.setState(new FeedBackState());
}
@Override
public String getCurrentState() {
return StateEnum.GENERATE.getValue();
}
}
/**
* 未支付状态
*/
class NotPayState extends AbstractState {
@Override
public void payOrderEvent(Context context) {
context.setState(new PaidState());
}
@Override
public void feedBackEvent(Context context) {
context.setState(new FeedBackState());
}
@Override
public String getCurrentState() {
return StateEnum.NOT_PAY.getValue();
}
}
/**
* 已支付状态
*/
class PaidState extends AbstractState {
@Override
public void feedBackEvent(Context context) {
context.setState(new FeedBackState());
}
@Override
public String getCurrentState() {
return StateEnum.PAID.getValue();
}
}
/**
* 发布状态
*/
class PublishState extends AbstractState {
@Override
public void acceptOrderEvent(Context context) {
//接受订单成功,把当前状态设置为NotPayState
//至于实际上应该变成哪个状态,由流程图来决定
context.setState(new NotPayState());
}
@Override
public void notPeopleAcceptEvent(Context context) {
context.setState(new FeedBackState());
}
@Override
public String getCurrentState() {
return StateEnum.PUBLISHED.getValue();
}
}
/**
* 回顾状态
*/
class ReviewState extends AbstractState {
@Override
public void makePriceEvent(Context context) {
context.setState(new PublishState());
}
@Override
public String getCurrentState() {
return StateEnum.REVIEWED.getValue();
}
}
e.环境上下文:
package com.atguigu.state.money;
/**
* 环境上下文
*/
public class Context extends AbstractState{
/**
* 当前的状态 state, 根据我们的业务流程处理,不停的变化
*/
private State state;
@Override
public void checkEvent(Context context) {
state.checkEvent(this);
getCurrentState();
}
@Override
public void checkFailEvent(Context context) {
state.checkFailEvent(this);
getCurrentState();
}
@Override
public void makePriceEvent(Context context) {
state.makePriceEvent(this);
getCurrentState();
}
@Override
public void acceptOrderEvent(Context context) {
state.acceptOrderEvent(this);
getCurrentState();
}
@Override
public void notPeopleAcceptEvent(Context context) {
state.notPeopleAcceptEvent(this);
getCurrentState();
}
@Override
public void payOrderEvent(Context context) {
state.payOrderEvent(this);
getCurrentState();
}
@Override
public void orderFailureEvent(Context context) {
state.orderFailureEvent(this);
getCurrentState();
}
@Override
public void feedBackEvent(Context context) {
state.feedBackEvent(this);
getCurrentState();
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
@Override
public String getCurrentState() {
System.out.println("当前状态 : " + state.getCurrentState());
return state.getCurrentState();
}
}
e.客户端测试类:
package com.atguigu.state.money;
/**
* 测试类
*/
public class ClientTest {
public static void main(String[] args) {
//创建context 对象
Context context = new Context();
//将当前状态设置为 PublishState
context.setState(new PublishState());
System.out.println(context.getCurrentState());
// //publish --> not pay
context.acceptOrderEvent(context);
// //not pay --> paid
context.payOrderEvent(context);
// // 失败, 检测失败时,会抛出异常
// try {
// context.checkFailEvent(context);
// System.out.println("流程正常..");
// } catch (Exception e) {
// System.out.println(e.getMessage());
// }
}
}
f.测试结果:
当前状态 : PUBLISHED
PUBLISHED
当前状态 : NOT_PAY
当前状态 : PAID
Process finished with exit code 0
5.金库警报系统
5.1.系统的运行逻辑:
5.2.类图:
5.3.伪代码:
- 1.传统方式:
- 2.状态模式:
5.4.编码实现:
a.状态接口:
package com.atguigu.state.Sample;
public interface State {
/**
* 设置时间
*
* @param context
* @param hour
*/
public abstract void doClock(Context context, int hour);
/**
* 使用金库
*
* @param context
*/
public abstract void doUse(Context context);
/**
* 按下警铃
*
* @param context
*/
public abstract void doAlarm(Context context);
/**
* 正常通话
*
* @param context
*/
public abstract void doPhone(Context context);
}
b.白天状态
package com.atguigu.state.Sample;
/**
* 表示白天的状态
*/
public class DayState implements State {
/**
* 每个状态都是一个类,如果每次改变状态都需要生成一个新的实例的话,比较浪费内存和时间,所以在这里使用单例模式
*/
private static DayState singleton = new DayState();
/**
* 构造函数的可见性是private
*/
private DayState() {
}
/**
* 获取唯一实例
* @return
*/
public static State getInstance() {
return singleton;
}
/**
* 设置时间
* @param context
* @param hour
*/
public void doClock(Context context, int hour) {
if (hour < 9 || 17 <= hour) {
// 如果时间是晚上,切换到夜间状态
context.changeState(NightState.getInstance());
}
}
/**
* 使用金库
* @param context
*/
public void doUse(Context context) {
context.recordLog("使用金库(白天)");
}
/**
* 按下警铃
* @param context
*/
public void doAlarm(Context context) {
context.callSecurityCenter("按下警铃(白天)");
}
/**
* 正常通话
* @param context
*/
public void doPhone(Context context) {
context.callSecurityCenter("正常通话(白天)");
}
/**
* 显示表示类的文字
* @return
*/
public String toString() {
return "[白天]";
}
}
c.夜间状态
package com.atguigu.state.Sample;
public class NightState implements State {
private static NightState singleton = new NightState();
/**
* 构造函数的可见性是private
*/
private NightState() {
}
/**
* 获取唯一实例
* @return
*/
public static State getInstance() {
return singleton;
}
/**
* 设置时间
* @param context
* @param hour
*/
public void doClock(Context context, int hour) {
if (9 <= hour && hour < 17) {
context.changeState(DayState.getInstance());
}
}
/**
* 使用金库
* @param context
*/
public void doUse(Context context) {
context.callSecurityCenter("紧急:晚上使用金库!");
}
/**
* 按下警铃
* @param context
*/
public void doAlarm(Context context) {
context.callSecurityCenter("按下警铃(晚上)");
}
/**
* 正常通话
* @param context
*/
public void doPhone(Context context) {
context.recordLog("晚上的通话录音");
}
/**
* 显示表示类的文字
* @return
*/
public String toString() {
return "[晚上]";
}
}
d.Context接口:
- 1.负责管理状态和联系警报中心
package com.atguigu.state.Sample;
public interface Context {
/**
* 设置时间
*
* @param hour
*/
public abstract void setClock(int hour);
/**
* 改变状态
*
* @param state
*/
public abstract void changeState(State state);
/**
* 联系警报中心
*
* @param msg
*/
public abstract void callSecurityCenter(String msg);
/**
* 在警报中心留下记录
*
* @param msg
*/
public abstract void recordLog(String msg);
}
Context角色:SafeFrame:
- 1.在这个实例程序中,Context角色的作用被Context接口和SafeFrame类分担了。Context接口定义了供外部调用者使用状态模式的接口,而SafeFrame类持有表示当前状态的ConcreteState角色
package com.atguigu.state.Sample;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class SafeFrame extends Frame implements ActionListener, Context {
/**
* 显示当前时间
*/
private TextField textClock = new TextField(60);
/**
* 显示警报中心的记录
*/
private TextArea textScreen = new TextArea(10, 60);
/**
* 金库使用按钮
*/
private Button buttonUse = new Button("使用金库");
/**
* 按下警铃按钮
*/
private Button buttonAlarm = new Button("按下警铃");
/**
* 正常通话按钮
*/
private Button buttonPhone = new Button("正常通话");
/**
* 结束按钮
*/
private Button buttonExit = new Button("结束");
/**
* 金库当前的状态
*/
private State state = DayState.getInstance();
/**
* 构造函数
*
* @param title
*/
public SafeFrame(String title) {
super(title);
setBackground(Color.lightGray);
setLayout(new BorderLayout());
// 配置textClock
add(textClock, BorderLayout.NORTH);
textClock.setEditable(false);
// 配置textScreen
add(textScreen, BorderLayout.CENTER);
textScreen.setEditable(false);
// 为界面添加按钮
Panel panel = new Panel();
panel.add(buttonUse);
panel.add(buttonAlarm);
panel.add(buttonPhone);
panel.add(buttonExit);
// 配置界面
add(panel, BorderLayout.SOUTH);
// 显示
pack();
show();
// 设置监听器
buttonUse.addActionListener(this);
buttonAlarm.addActionListener(this);
buttonPhone.addActionListener(this);
buttonExit.addActionListener(this);
}
/**
* 按钮被按下后,该方法会被调用
*
* @param e
*/
public void actionPerformed(ActionEvent e) {
System.out.println(e.toString());
if (e.getSource() == buttonUse) {
// 金库使用按钮,并不需要去判断状态,直接调用即可
state.doUse(this);
} else if (e.getSource() == buttonAlarm) {
// 按下警铃按钮
state.doAlarm(this);
} else if (e.getSource() == buttonPhone) {
// 正常通话按钮
state.doPhone(this);
} else if (e.getSource() == buttonExit) {
// 结束按钮
System.exit(0);
} else {
System.out.println("?");
}
}
/**
* 设置时间
*
* @param hour
*/
public void setClock(int hour) {
String clockstring = "现在时间是";
if (hour < 10) {
clockstring += "0" + hour + ":00";
} else {
clockstring += hour + ":00";
}
System.out.println(clockstring);
// 将当前时间显示在界面的上方
textClock.setText(clockstring);
// 进行当前状态下的处理
state.doClock(this, hour);
}
/**
* 改变状态
*
* @param state
*/
public void changeState(State state) {
System.out.println("从" + this.state + "状態变为了" + state + "状态。");
this.state = state;
}
/**
* 联系警报中心
*
* @param msg
*/
public void callSecurityCenter(String msg) {
textScreen.append("call! " + msg + "\n");
}
/**
* 在警报中心留下记录
*
* @param msg
*/
public void recordLog(String msg) {
textScreen.append("record ... " + msg + "\n");
}
}
客户端测试:
package com.atguigu.state.Sample;
public class Main {
public static void main(String[] args) {
SafeFrame frame = new SafeFrame("State Sample");
while (true) {
for (int hour = 0; hour < 24; hour++) {
// 设置时间
frame.setClock(hour);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
}
}
运行结果:
现在时间是00:00
从[白天]状態变为了[晚上]状态。
现在时间是01:00
现在时间是02:00
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=使用金库,when=1691920919394,modifiers=] on button0
现在时间是03:00
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=按下警铃,when=1691920920040,modifiers=] on button1
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=正常通话,when=1691920920824,modifiers=] on button2
现在时间是04:00
现在时间是05:00
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=按下警铃,when=1691920922071,modifiers=] on button1
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=使用金库,when=1691920922626,modifiers=] on button0
现在时间是06:00
现在时间是07:00
现在时间是08:00
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=使用金库,when=1691920925446,modifiers=] on button0
现在时间是09:00
从[晚上]状態变为了[白天]状态。
现在时间是10:00
现在时间是11:00
现在时间是12:00
现在时间是13:00
现在时间是14:00
现在时间是15:00
Disconnected from the target VM, address: '127.0.0.1:1966', transport: 'socket'
Process finished with exit code 130
5.5.分析
a.概述:
- 1.上面的实现方式中,由具体状态类来实际调用方法切换到另一个状态,如DayState类的doClock方法,这种方式既有优点又有缺点:
- 优点:当我们想知道“什么时候从DayState的类变化为其他状态”,只需要查看DayState类即可
- 缺点:每个ConcreteState角色都需要知道其他ConcreteState角色的方法,各个类之间的依赖关系较强,如果删除了一个ConcreteState类,就需要修改其他的ConcreteState类
b.其他实现方式
- 除了这种实现方式之外,也可以将所有的状态迁移交给扮演Context角色的类来负责,这样可以提高ConcreteState角色的独立性,程序的整体结构也会更加清晰,当然,这样做需要Context角色知道所有的ConcreteState角色,可以使用
中介者模式
来改进
b1.问题一
- 问:将Context定义为抽象类而非接口,然后让Context类持有state字段这样更符合状态模式的设计思想。但是在示例程序中我们并没有这么做,而是将Context角色定义为Context接口,让SafeFrame类持有state字段,请问这是为什么呢?
答:Java中只能单一继承,所以如果将Context角色定义为类,那么由于SafeFrame类已经是Frame类的子类了,它将无法再继承Context 类。不过,如果另外编写一个Context类的子类,并将它的实例保存在SafeFrame类的字段中那么通过将处理委托给这个实例是可以实现上述问题的需求的。
b2.问题二
- 1.请在示例程序中增加一个新的“紧急情况”状态。不论是什么时间,只要处于“紧急情况”下,就向警报中心通知紧急情况
- 按下警铃后,系统状态变为“紧急情况”状态
- 如果“紧急情况”下使用金库的话,会向警报中心通知紧急情况(与当时的时间无关)
- 如果“紧急情况”下按下警铃的话,会向警报中心通知紧急情况(与当时的时间无关)
- 如果“紧急情况”下使用电话的话,会呼叫警报中心的留言电话(与当时的时间无关)
b3.代码实现:
【增加一个紧急状态类】
package com.atguigu.state.A4;
public class UrgentState implements State {
private static UrgentState singleton = new UrgentState();
private UrgentState() {
}
public static State getInstance() {
return singleton;
}
public void doClock(Context context, int hour) {
// 设置时间
// 在设置时间处理中什么都不做
}
public void doUse(Context context) {
// 使用金库
context.callSecurityCenter("紧急:紧急时使用金库!");
}
public void doAlarm(Context context) {
// 按下警铃
context.callSecurityCenter("按下警铃(紧急时)");
}
public void doPhone(Context context) {
// 正常通话
context.callSecurityCenter("正常通话(紧急时)");
}
public String toString() {
// 显示字符串
return "[紧急时]";
}
}
【修改其他状态的状态迁移方法】
package com.atguigu.state.A4;
public class DayState implements State {
private static DayState singleton = new DayState();
private DayState() {
}
public static State getInstance() {
return singleton;
}
public void doClock(Context context, int hour) {
if (hour < 9 || 17 <= hour) {
context.changeState(NightState.getInstance());
}
}
public void doUse(Context context) {
context.recordLog("使用金库(白天)");
}
public void doAlarm(Context context) {
context.callSecurityCenter("按下警铃(白天)");
// 只需要看这里就行,一旦按下紧铃,就会进入到紧急状态
context.changeState(UrgentState.getInstance());
}
public void doPhone(Context context) {
context.callSecurityCenter("正常通话(白天)");
}
public String toString() {
return "[白天]";
}
}
夜间状态也需要修改对应的状态迁移方法,和白天状态类似,这里就不再展示了
参考文档:
本文章为本人学习尚硅谷与黑马程序员的学习笔记以及参考了一些博客文档,发布文章是想帮助其他学习的人更方便地整理自己的笔记或者直接通过文章学习相关知识,如有侵权请联系删除,最后对开源分享的视频与参考的博客文档作者表示感谢。
- 1.视频
- 2.博客: