以抽奖活动引出状态模式
假如每参加一次抽奖活动要扣除用户 50 积分,中奖概率是 10%,奖品数量固定,抽完就不能抽奖。
活动有四个状态:可以抽奖、不能抽奖、发放奖品和奖品领完
活动的四个状态转换关系图
一、状态模式
1、基本介绍
状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换。
当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类
- Context:环境角色,用于维护 State 实例,这个实例定义当前状态
- State:是抽象状态角色,封装与 Context 的一个特点接口相关行为
- ConcreteState:具体的状态角色,每个子类实现一个与 Context 的一个状态相关行为
2、代码实现
具体思路
根据抽奖状态关系转换图,可以在抽象状态中定义三个方法
- 扣除积分(每次抽奖前都要扣除积分)
- 是否中奖(抽奖时有两种情况,中奖和不中奖)
- 领取奖品(领取奖品有两种情况,有奖品和没有奖品)
抽象状态类
public abstract class State {
/**
* 扣除积分 -50
*/
public abstract void deductIntegral();
/**
* 是否抽中奖品
* @return
*/
public abstract boolean raffle();
/**
* 发放奖品
*/
public abstract void dispensePrize();
}
不能抽奖状态
提前说明: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("没参与抽奖,不能发放奖品");
}
}
抽奖状态
//抽奖状态
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("没中奖,不给奖品");
}
}
发放奖品状态
//发放奖品状态
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());
}
}
}
奖品领完状态
//奖品领完状态
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("奖品都没了,不用来领奖了!");
}
}
抽奖活动类
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;
}
}
测试
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();
}
}
}
结果
快来玩啊,抽奖活动开始了
-----第1次抽奖-----
扣除50积分成功,您可以抽奖了
要抽奖啦
恭喜你,领取奖品成功,奖品剩余 1个
-----第2次抽奖-----
扣除50积分成功,您可以抽奖了
要抽奖啦
没抽中奖品,再接再厉
-----第3次抽奖-----
扣除50积分成功,您可以抽奖了
要抽奖啦
恭喜你,领取奖品成功,奖品剩余 0个
-----第4次抽奖-----
扣除50积分成功,您可以抽奖了
要抽奖啦
没抽中奖品,再接再厉
-----第5次抽奖-----
扣除50积分成功,您可以抽奖了
要抽奖啦
没抽中奖品,再接再厉
-----第6次抽奖-----
扣除50积分成功,您可以抽奖了
要抽奖啦
没抽中奖品,再接再厉
-----第7次抽奖-----
扣除50积分成功,您可以抽奖了
要抽奖啦
没抽中奖品,再接再厉
-----第8次抽奖-----
扣除50积分成功,您可以抽奖了
要抽奖啦
没抽中奖品,再接再厉
-----第9次抽奖-----
扣除50积分成功,您可以抽奖了
要抽奖啦
不好意思,没有奖品了
-----第10次抽奖-----
奖品没了,就不扣您的积分了
奖品都没了,您还抽什么奖啊!
二、状态模式的注意事项和细节
1)代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中
2)方便维护。将容易产生问题的 if-else 语句删除了,如果把每个状态的行为都放到一个类中,每次调用方法时都要判断当前是什么状态,不但会产出很多 if-else 语句,而且容易出错。
3)符合“开闭原则”。容易增删状态
4)会产生很多类。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度
应用场景:当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状态要求有不同的行为的时候,可以考虑使用状态模式