前言介绍


接下里介绍的是Java 的设计模式之一:状态模式

咱们还是以一个问题进行开展,引入状态模式

咱们当初有一个抽奖APP须要编写

请编写程序实现 APP 抽奖流动 具体要求如下:

流动有四个状态: 能够抽奖、不能抽奖、发放奖品和奖品领完

每加入一次这个流动要扣除用户 50 积分,中奖概率是 10%

奖品数量固定,数量抽完就不能抽奖

请问你怎么制作这个程序?

咱们举荐你应用状态模式

一、什么是状态模式

状态模式(State Pattern):它次要用来解决对象在多种状态转换时,须要对外输入不同的行为的问题。

状态和行为是一一对应的,状态之间能够互相转换

当一个对象的外在状态扭转时,容许扭转其行为,这个对象看起来像是扭转了其类

状态模式原理类图

状态模式下的角色及职责

Context:类为环境角色用于保护 State 实例,这个实例定义以后状态

State:是形象状态角色,定义一个接口封装与 Context 的一个特点接口相干行为

ConcreteState:具体的状态角色,每个子类实现一个与 Context 的一个状态相干行为

二、应用状态模式解决问题

接下来咱们依据思路,创立不同状态的抽象类

/** *状态抽象类 *@author Administrator * */abstract class State {    // 扣除积分 - 50    public abstract void deductMoney();    // 是否抽中奖品    public abstract boolean raffle();    // 发放奖品    public abstract void dispensePrize();}

接下里依据思路创立不同状态具体子类(可抽、不可抽、发放、发完)

/** * 能够抽奖的状态 * * @author Administrator */class CanRaffleState extends State {    //曾经扣除了积分,不能再扣    @Override    public void deductMoney() {        System.out.println("曾经扣取过了积分");    }    //能够抽奖, 抽完奖后,依据理论状况,改成新的状态    @Override    public boolean raffle() {        System.out.println("正在抽奖,请稍等!");    }    // 不能发放奖品    @Override    public void dispensePrize() {        System.out.println("没中奖,不能发放奖品");    }}
/** * 不能抽奖状态 * * @author Administrator */class NoRaffleState extends State {    // 以后状态能够扣积分 , 扣除后,将状态设置成能够抽奖状态    @Override    public void deductMoney() {        System.out.println("扣除 50 积分胜利,您能够抽奖了");    }    // 以后状态不能抽奖    @Override    public boolean raffle() {        System.out.println("扣了积分能力抽奖喔!");        return false;    }    // 以后状态不能发奖品    @Override    public void dispensePrize() {        System.out.println("不能发放奖品");    }}
/** * 发放奖品的状态 * * @author Administrator */class DispenseState extends State {    @Override    public void deductMoney() {        System.out.println("不能扣除积分");    }    @Override    public boolean raffle() {        System.out.println("不能抽奖");        return false;    }    //发放奖品 @Override    public void dispensePrize() {        System.out.println("正在发送奖品中.....");    }}
/** * 奖品发放结束状态 * 阐明,当咱们 activity 扭转成 DispenseOutState, 抽奖流动完结 * * @author Administrator */class DispenseOutState extends State {    @Override    public void deductMoney() {        System.out.println("奖品发送完了,请下次再加入");    }    @Override    public boolean raffle() {        System.out.println("奖品发送完了,请下次再加入");        return false;    }    @Override    public void dispensePrize() {        System.out.println("奖品发送完了,请下次再加入");    }}

接下来咱们依据思路,创立一个流动类治理不同的状态,同时初始化奖品数量、与初始化为不可抽奖的状态

/** * 抽奖流动 * * @author Administrator */class RaffleActivity {    // state 示意流动以后的状态,是变动    State state = null;        // 奖品数量    int count = 0;    //结构器    //1. 初始化以后的状态为 noRafflleState(即不能抽奖的状态)    //2. 初始化奖品的数量    public RaffleActivity(int count) {        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;    }}

接下里咱们使不同的状态类与流动类进行关联(可抽、不可抽、发放、发完)

/** * 能够抽奖的状态 * * @author Administrator */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("没中奖,不能发放奖品");    }}
/** * 不能抽奖状态 * * @author Administrator */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("不能发放奖品");    }}
/** * 发放奖品的状态 * * @author Administrator */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);        }    }}
/** * 奖品发放结束状态 * 阐明,当咱们 activity 扭转成 DispenseOutState, 抽奖流动完结 * * @author Administrator */class DispenseOutState extends State {    // 初始化时传入流动援用    RaffleActivity activity;    public DispenseOutState(RaffleActivity activity) {        this.activity = activity;    }    //省略其余关键性代码.....}

而流动订单则引入四种不同的状态,并且实现初始化

/** * 抽奖流动 // * * @author Administrator */class RaffleActivity {    //省略其余关键性代码.....        // 四个属性,示意四种状态    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;    }    public State getNoRafflleState() {        return noRafflleState;    }    public void setNoRafflleState(State noRafflleState) {        this.noRafflleState = noRafflleState;    }    public State getCanRaffleState() {        return canRaffleState;    }    public void setCanRaffleState(State canRaffleState) {        this.canRaffleState = canRaffleState;    }    public State getDispenseState() {        return dispenseState;    }    public void setDispenseState(State dispenseState) {        this.dispenseState = dispenseState;    }    public State getDispensOutState() {        return dispensOutState;    }    public void setDispensOutState(State dispensOutState) {        this.dispensOutState = dispensOutState;    }}

接下来咱们应用demo,领会一下这种设计模式下的抽奖流动

public static void main(String[] args) {    // 创建活动对象,奖品有 1 个奖品    RaffleActivity activity = new RaffleActivity(1);    // 咱们间断抽 300 次奖    for (int i = 0; i < 3; i++) {        System.out.println("--------第" + (i + 1) + "次抽奖----------");        // 加入抽奖,第一步点击扣除积分        activity.debuctMoney();        // 第二步抽奖        activity.raffle();    }}运行后果如下:--------第1次抽奖----------扣除 50 积分胜利,您能够抽奖了正在抽奖,请稍等!很遗憾没有抽中奖品!--------第2次抽奖----------扣除 50 积分胜利,您能够抽奖了正在抽奖,请稍等!很遗憾没有抽中奖品!--------第3次抽奖----------扣除 50 积分胜利,您能够抽奖了正在抽奖,请稍等!很遗憾没有抽中奖品!

三、通过我的项目领会状态模式

当初有一个借贷平台,他的订单有审核-公布-抢单等等步骤,随着操作的不同,会扭转订单的状态, 我的项目中的这个模块实现就会应用到状态模式

咱们先看看借贷平台的订单流程是怎么样的呢?

有不同的审核状况

订单生成后,进行审核,通过后公布,2小时后并没有人接单则间接完结

订单生成后,进行审核,审核不通过,则间接完结

订单生成后,进行审核,通过后公布,2小时内有人接单则进入待付款状态

  • 待付款操作,进行付款,实现反馈,完结订单
  • 待付款操作,有人付款胜利,则其他人再接到这个订单,则失败

依据订单流程,咱们再设计一张横纵坐标示意关系

应用状态模式下的原理类图

接下来依据思路,咱们创立接口寄存不同的状态办法

/** *状态接口 *@author Administrator * */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();}

接下里咱们依据思路创立抽象类,对接口实现默认空办法

abstract class AbstractState implements State {    protected static final RuntimeException EXCEPTION = new RuntimeException("操作流程不容许");    //抽象类,默认实现了 State 接口的所有办法    //该类的所有办法,其子类(具体的状态类),能够有抉择的进行重写    @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; }}

接下来咱们依据思路,让各种状态的实现类实现各自不同的状态

//已完结状态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();    }}

接下来依据思路,创立环境上下文治理所有状态

//环境上下文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();    }}

同时咱们应用枚举的形式连贯状态类

/** *状态枚举类 *@author Administrator * */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; }}

接下来咱们应用demo ,领会状态模式下的借贷平台的状态机制

public static void main(String[] args) {     //创立 context 对象     Context context = new Context();     //将以后状态设置为 PublishState     context.setState(new PublishState());     //将publish状态改为 --> not pay状态     context.acceptOrderEvent(context);     //将not pay 状态改为--> paid状态     context.payOrderEvent(context);}运行后果如下:以后状态 : NOT_PAY以后状态 : PAID

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


代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中

不便保护。将容易产生问题的 if-else 语句删除了,如果把每个状态的行为都放到一个类中,每次调用办法时都要判断以后是什么状态,岂但会产出很多 if-else 语句,而且容易出错

合乎“开闭准则”。容易增删状态

然而会产生很多类,每个状态都要一个对应的类,当状态过多时会产生很多类,加大保护难度

利用场景:当一个事件或者对象有很多种状态,状态之间会互相转换,对不同的状态要求有不同的行为的时候, 能够思考应用状态模式

参考资料


尚硅谷:设计模式(韩顺平老师):备忘录模式

Refactoring.Guru:《深刻设计模式》