1、APP 抽奖活动问题

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

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

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

3)活动有四个状态:可以抽奖、不能抽奖、发放奖品和奖品领完

4)活动的四个状态转换关系图

7r8n8

2、状态模式

2.1 基本介绍

1)状态模式(State Pattern):他主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换。

2)当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类。

2.2 原理类图

9h74a

说明(角色和职责):

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

2)State 是抽象状态角色,定义一个接口封装与 Context 的一个特定接口相关行为

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

2.3 应用实例

1)要求

完成 APP 抽奖活动项目,使用状态模式

2)思路分析和图解(类图)

  • 定义出一个接口叫状态接口,每个状态都实现它
  • 接口有扣除积分的方法、抽奖方法、发放奖品方法

0i3ue

3)代码实现

State 状态抽象类

package com.wenze.design.state;  
  
/**  
 * 状态抽象类  
 *  
 * @author 汶泽  
 * @version 1.0  
 * @date 2023-11-15 10:32:09  
 * @since 1.0  
 */
public abstract class State {  
    // 扣除积分 - 50 分  
    public abstract void deductMoney();  
    // 是否抽中奖品  
    public abstract boolean raffle();  
    // 发放奖品  
    public abstract void dispensePrize();  
}

RaffleActivity 抽奖的活动类

package com.wenze.design.state;  
  
import lombok.Getter;  
import lombok.Setter;  
  
/**  
 * 抽奖的活动类  
 *  
 * @author 汶泽  
 * @version 1.0  
 * @date 2023-11-15 10:34:38  
 * @since 1.0  
 */
@Getter  
@Setter  
public class RaffleActivity {  
    // 表示活动当前的状态,是变化的  
    private State state = null;  
    // 奖品数量  
    private Integer count = 0;  
  
    // 四个属性,表示四种状态  
    private State noRaffleState = new NoRaffleState(this);  
    private State canRaffleState = new CanRaffleState(this);  
    private State dispenseState = new DispenseState(this);  
    private State dispenseOutState = new DispenseOutState(this);  
    public RaffleActivity(final Integer count) {  
        this.state = getNoRaffleState();  
        this.count = count;  
    }  
    // 扣分,调用当前状态的 deductMoney    public void deductMoney() {  
        state.deductMoney();  
    }  
    // 抽奖  
    public void raffle() {  
        if (state.raffle()) {  
            // 如果中奖,领取奖品  
            state.dispensePrize();  
        }  
    }  
    public Integer getCount() {  
        int curCount = count;  
        count--;  
        return curCount;  
    }  
}

NoRaffleState 不能抽奖的状态

package com.wenze.design.state;  
  
import lombok.AllArgsConstructor;  
  
/**  
 * 不能抽奖的状态  
 *  
 * @author 汶泽  
 * @version 1.0  
 * @date 2023-11-15 10:33:53  
 * @since 1.0  
 */
@AllArgsConstructor  
public class NoRaffleState extends State {  
    // 初始化时传入活动引用,扣除积分后改变其状态  
    private RaffleActivity 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("不能发放奖品");  
    }  
}

CanRaffleState 可以抽奖的状态

package com.wenze.design.state;  
  
import lombok.AllArgsConstructor;  
  
import java.util.Random;  
  
/**  
 * 可以抽奖的状态  
 *  
 * @author 汶泽  
 * @version 1.0  
 * @date 2023-11-15 10:42:58  
 * @since 1.0  
 */
@AllArgsConstructor  
public class CanRaffleState extends State {  
    private RaffleActivity activity;  
    // 已经扣除过积分,不能再扣  
    @Override  
    public void deductMoney() {  
        System.out.println("已经扣除过积分");  
    }  
    // 可以抽奖,抽完奖后,根据实际情况,改成新的状态  
    @Override  
    public boolean raffle() {  
        System.out.println("正在抽奖,请稍等");  
        final Random random = new Random();  
        final int number = random.nextInt(10);  
        // 10%的中奖几率  
        if (number == 0) {  
            // 改变活动状态为发放奖品  
            activity.setState(activity.getDispenseState());  
            return true;  
        }  
        System.out.println("很遗憾没有抽中奖品");  
        // 改变状态为不能抽奖  
        activity.setState(activity.getNoRaffleState());  
        return false;  
    }  
    // 不能发放奖品  
    @Override  
    public void dispensePrize() {  
        System.out.println("不能发放奖品");  
    }  
}

DispenseState 发放奖品的状态

package com.wenze.design.state;  
  
import lombok.AllArgsConstructor;  
  
/**  
 * 发放奖品的状态  
 *  
 * @author 汶泽  
 * @version 1.0  
 * @date 2023-11-15 10:50:12  
 * @since 1.0  
 */
@AllArgsConstructor  
public class DispenseState extends State {  
    // 初始化时传入活动引用,发放奖品后改变其状态  
    private RaffleActivity 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.getNoRaffleState());  
        } else {  
            System.out.println("很遗憾,奖品发送完了");  
            // 改变状态为奖品发送完毕,后面我们就不可以抽奖  
            activity.setState(activity.getDispenseOutState());  
            // System.out.println("抽奖活动结束了");  
            // System.exit(0);      
        }  
    }  
}

DispenseOutState 奖品发放完的状态

package com.wenze.design.state;  
  
import lombok.AllArgsConstructor;  
  
/**  
 * 奖品发放完毕的状态  
 *  
 * @author 汶泽  
 * @version 1.0  
 * @date 2023-11-15 11:00:23  
 * @since 1.0  
 */
@AllArgsConstructor  
public class DispenseOutState extends State {  
    private RaffleActivity activity;  
    @Override  
    public void deductMoney() {  
        System.out.println("奖品发放完毕,请下次再参加");  
    }  
    @Override  
    public boolean raffle() {  
        System.out.println("奖品发放完毕,请下次再参加");  
        return false;  
    }  
    @Override  
    public void dispensePrize() {  
        System.out.println("奖品发放完毕,请下次再参加");  
    }  
}

Client 客户端

package com.wenze.design.state;  
  
/**  
 * 客户端  
 *  
 * @author 汶泽  
 * @version 1.0  
 * @date 2023-11-15 11:06:00  
 * @since 1.0  
 */
public class Client {  
    public static void main(String[] args) {  
        // 创建抽奖活动,奖品数量为 1 个  
        RaffleActivity raffleActivity = new RaffleActivity(1);  
        // 我们连续抽 30 次奖  
        for (int i = 0; i < 30; i++) {  
            System.out.println("-------第 " + (i + 1) + " 次抽奖--------");  
            // 参加抽奖,第一步点击扣除积分  
            raffleActivity.deductMoney();  
            // 第二步抽奖  
            raffleActivity.raffle();  
        }  
    }  
}

3、状态模式在实际项目-借贷平台的源码剖析

1)借贷平台的订单,有审核-发布-抢单等等步骤,随着操作的不同,会改变订单的状态,项目中的这个模块实现就会使用到状态模式

2)通常通过 if/else 判断订单的状态,从而实现不同的逻辑,伪代码如下:

if (审核) {
	// 审核逻辑
} else if (发布) {
	// 发布逻辑
} else if (接单) {
	// 接单逻辑
}

问题分析:

这类代码难以应对变化,在添加一种状态时,我们需要手动添加 if/else,在添加一种功能时,要对所有的状态进行判断。因此代码会变得越来越臃肿,并且一旦没有处理某个状态,变回发生极其严重的 bug,难以维护

3)使用状态模式完成借贷平台的项目的审核模块【设计+代码】

流程图:

p2mdp

类图:

zb7dl

代码:

State 状态接口

package com.wenze.design.state.money;  
  
/**  
 * 状态接口  
 *  
 * @author 汶泽  
 * @version 1.0  
 * @date 2023-11-15 11:44:20  
 * @since 1.0  
 */
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();  
}

StateEnum 状态类型枚举

package com.wenze.design.state.money;  
  
import lombok.AllArgsConstructor;  
import lombok.Getter;  
  
/**  
 * 状态枚举  
 *  
 * @author 汶泽  
 * @version 1.0  
 * @date 2023-11-15 11:50:16  
 * @since 1.0  
 */
@Getter  
@AllArgsConstructor  
public enum StateEnum {  
    FEED_BACKED("FEED_BACK"),  
    GENERATE("GENERATE"),  
    NOT_PAY("NOT_PAY"),  
    PAID("PAID"),  
    PUBLISHED("PUBLISHED"),  
    REVIEW("REVIEW"),  
    ;    private final String value;  
}

AbstractState 状态抽象类

package com.wenze.design.state.money;  
  
/**  
 * 状态接口抽象类  
 *  
 * @author 汶泽  
 * @version 1.0  
 * @date 2023-11-15 11:47:30  
 * @since 1.0  
 */
public abstract class AbstractState implements State {  
    protected static final RuntimeException EXCEPTION = new RuntimeException("操作流程不允许");  
    /**  
     * 电审  
     */  
    @Override  
    public void checkEvent(final Context context) {  
        throw EXCEPTION;  
    }  
    /**  
     * 电审失败  
     */  
    @Override  
    public void checkFailEvent(final Context context) {  
        throw EXCEPTION;  
    }  
    /**  
     * 定价发布  
     */  
    @Override  
    public void makePriceEvent(final Context context) {  
        throw EXCEPTION;  
    }  
    /**  
     * 接单  
     */  
    @Override  
    public void acceptOrderEvent(final Context context) {  
        throw EXCEPTION;  
    }  
    /**  
     * 无人接单失败  
     */  
    @Override  
    public void notPeopleAcceptEvent(final Context context) {  
        throw EXCEPTION;  
    }  
    /**  
     * 付款  
     */  
    @Override  
    public void payOrderEvent(final Context context) {  
        throw EXCEPTION;  
    }  
    /**  
     * 接单有人支付失败  
     */  
    @Override  
    public void orderFailureEvent(final Context context) {  
        throw EXCEPTION;  
    }  
    /**  
     * 反馈  
     */  
    @Override  
    public void feedBackEvent(final Context context) {  
        throw EXCEPTION;  
    }  
    @Override  
    public abstract String getCurrentState();  
}

AllState 所有状态类

package com.wenze.design.state.money;  
  
/**  
 * 所有的状态类  
 *  
 * @author 汶泽  
 * @version 1.0  
 * @date 2023-11-15 11:50:25  
 * @since 1.0  
 */
class FeedBackState extends AbstractState {  
    @Override  
    public String getCurrentState() {  
        return StateEnum.FEED_BACKED.getValue();  
    }  
}  
class GenerateState extends AbstractState {  
    @Override  
    public void checkEvent(final Context context) {  
        context.setState(new ReviewState());  
    }  
    @Override  
    public void checkFailEvent(final Context context) {  
        context.setState(new FeedBackState());  
    }  
    @Override  
    public String getCurrentState() {  
        return StateEnum.GENERATE.getValue();  
    }  
}  
class NotPayState extends AbstractState {  
    @Override  
    public void payOrderEvent(final Context context) {  
        context.setState(new PaidState());  
    }  
    @Override  
    public void feedBackEvent(final Context context) {  
        context.setState(new FeedBackState());  
    }  
    @Override  
    public String getCurrentState() {  
        return StateEnum.NOT_PAY.getValue();  
    }  
}  
class PaidState extends AbstractState {  
    @Override  
    public void feedBackEvent(final Context context) {  
        context.setState(new FeedBackState());  
    }  
    @Override  
    public String getCurrentState() {  
        return StateEnum.PAID.getValue();  
    }  
}  
class PublishState extends AbstractState {  
    @Override  
    public void acceptOrderEvent(final Context context) {  
        // 将当前状态设置为【未付款】  
        // 至于变成哪个状态,由流程图来决定  
        context.setState(new NotPayState());  
    }  
    @Override  
    public void notPeopleAcceptEvent(final Context context) {  
        context.setState(new FeedBackState());  
    }  
    @Override  
    public String getCurrentState() {  
        return StateEnum.PUBLISHED.getValue();  
    }  
}  
class ReviewState extends AbstractState {  
    @Override  
    public void makePriceEvent(final Context context) {  
        context.setState(new PublishState());  
    }  
    @Override  
    public String getCurrentState() {  
        return StateEnum.REVIEW.getValue();  
    }  
}

Context 上下文

package com.wenze.design.state.money;  
  
import lombok.Getter;  
import lombok.Setter;  
  
/**  
 * 环境上下文  
 *  
 * @author 汶泽  
 * @version 1.0  
 * @date 2023-11-15 11:44:47  
 * @since 1.0  
 */
@Setter  
@Getter  
public class Context extends AbstractState {  
    // 当前的状态 state, 根据我们业务流程的处理,不停的变化  
    private State state;  
    @Override  
    public void checkEvent(final Context context) {  
        state.checkEvent(this);  
        getCurrentState();  
    }  
    @Override  
    public void checkFailEvent(final Context context) {  
        state.checkFailEvent(this);  
        getCurrentState();  
    }  
    @Override  
    public void makePriceEvent(final Context context) {  
        state.makePriceEvent(this);  
        getCurrentState();  
    }  
    @Override  
    public void acceptOrderEvent(final Context context) {  
        state.acceptOrderEvent(this);  
        getCurrentState();  
    }  
    @Override  
    public void notPeopleAcceptEvent(final Context context) {  
        state.notPeopleAcceptEvent(this);  
        getCurrentState();  
    }  
    @Override  
    public void payOrderEvent(final Context context) {  
        state.payOrderEvent(this);  
        getCurrentState();  
    }  
    @Override  
    public void orderFailureEvent(final Context context) {  
        state.orderFailureEvent(this);  
        getCurrentState();  
    }  
    @Override  
    public void feedBackEvent(final Context context) {  
        state.feedBackEvent(this);  
        getCurrentState();  
    }  
    @Override  
    public String getCurrentState() {  
        return "当前状态:" + state.getCurrentState();  
    }  
}

Client 客户端

package com.wenze.design.state.money;  
  
/**  
 * 客户端  
 *  
 * @author 汶泽  
 * @version 1.0  
 * @date 2023-11-15 15:39:26  
 * @since 1.0  
 */
public class Client {  
    public static void main(String[] args) {  
        // 创建 Context 对象  
        Context context = new Context();  
        context.setState(new PublishState());  
        System.out.println(context.getCurrentState());  
        // publish -> not pay  
        context.acceptOrderEvent(context);  
        System.out.println(context.getCurrentState());  
        // not pay -> paid  
        context.payOrderEvent(context);  
        System.out.println(context.getCurrentState());  
        // 失败,检测失败时,会抛出异常  
        try {  
            context.checkFailEvent(context);  
            System.out.println(context.getCurrentState());  
            System.out.println("流程正常");  
        } catch (Exception e) {  
            System.out.println(e.getMessage());  
        }  
    }  
}

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

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

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

3)符合“开闭原则”。容易增删状态

4)会产生很多类。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度

5)当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状态要求有不同的行为的时候,可以考虑使用状态模式。