抽奖问题
1.每参加一次抽奖需要50积分,中奖概率10%
2.奖品数量有限,没有即止
3.抽象由4个状态:可以抽奖、不能抽奖、发放奖品、奖品领完。
状态模式
状态模式:它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换
当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类
状态模式结构
1.上下文 (Context)
保存了对于一个具体状态对象的引用, 并会将所有与该状态相关的工作委派给它。 上下文通过状态接口与状态对象交互, 且会提供一个设置器用于传递新的状态对象。
2.状态 (State) 接口
会声明特定于状态的方法。 这些方法应能被其他所有具体状态所理解, 因为你不希望某些状态所拥有的方法永远不会被调用。
3.具体状态 (Concrete States)
会自行实现特定于状态的方法。 为了避免多个状态中包含相似代码, 你可以提供一个封装有部分通用行为的中间抽象类。
状态对象可存储对于上下文对象的反向引用。 状态可以通过该引用从上下文处获取所需信息, 并且能触发状态转移。
上下文和具体状态都可以设置上下文的下个状态, 并可通过替换连接到上下文的状态对象来完成实际的状态转换。
状态模式适合应用场景
1.如果对象需要根据自身当前状态进行不同行为, 同时状态的数量非常多且与状态相关的代码会频繁变更的话, 可使用状态模式。
2.如果某个类需要根据成员变量的当前值改变自身行为, 从而需要使用大量的条件语句时, 可使用该模式。
3.当相似状态和基于条件的状态机转换中存在许多重复代码时, 可使用状态模式。
实现方式
-
确定哪些类是上下文。 它可能是包含依赖于状态的代码的已有类; 如果特定于状态的代码分散在多个类中, 那么它可能是一个新的类。
-
声明状态接口。 虽然你可能会需要完全复制上下文中声明的所有方法, 但最好是仅把关注点放在那些可能包含特定于状态的行为的方法上。
-
为每个实际状态创建一个继承于状态接口的类。 然后检查上下文中的方法并将与特定状态相关的所有代码抽取到新建的类中。
在将代码移动到状态类的过程中, 你可能会发现它依赖于上下文中的一些私有成员。 你可以采用以下几种变通方式:
- 将这些成员变量或方法设为公有。
- 将需要抽取的上下文行为更改为上下文中的公有方法, 然后在状态类中调用。 这种方式简陋却便捷, 你可以稍后再对其进行修补。
- 将状态类嵌套在上下文类中。 这种方式需要你所使用的编程语言支持嵌套类。
-
在上下文类中添加一个状态接口类型的引用成员变量, 以及一个用于修改该成员变量值的公有设置器。
-
再次检查上下文中的方法, 将空的条件语句替换为相应的状态对象方法。
-
为切换上下文状态, 你需要创建某个状态类实例并将其传递给上下文。 你可以在上下文、 各种状态或客户端中完成这项工作。 无论在何处完成这项工作, 该类都将依赖于其所实例化的具体类。
状态模式优缺点
优点:
✔️单一职责原则。 将与特定状态相关的代码放在单独的类中。
✔️开闭原则。 无需修改已有状态类和上下文就能引入新状态。
✔️通过消除臃肿的状态机条件语句简化上下文代码。
缺点:
❌如果状态机只有很少的几个状态, 或者很少发生改变, 那么应用该模式可能会显得小题大作。
解决抽奖问题
代码:
//抽奖活动
public class Activity {
//当前状态
State state = null;
//奖品数量
int count = 0;
//四个状态
State noRaffleState = new NoRaffleState(this);
State canRaffleState =new CanRaffleState(this);
State dispenseState =new DispenseState(this);
State dispenseOutState =new DispenseOutState(this);
//默认为 不可抽奖、并定义奖品数量
public Activity(int count){
this.state = getNoRaffleState();
this.count = count;
}
//扣除积分
public void deductMoney(){
state.deductMoney();
}
//抽奖
public void raffle(){
//抽奖成功则领取奖品
if (state.raffle()){
state.dispensePrize();
}
}
//此处,每获取一次奖品,则count--
public int getCount() {
int afterCount = count;
count--;
return afterCount;
}
//get&set方法
}
public abstract class State {
//抽奖条件:-50积分
public abstract void deductMoney();
//是否抽中商品
public abstract boolean raffle();
//发奖品
public abstract void dispensePrize();
}
//不能抽奖
public class NoRaffleState extends State {
Activity activity;
public NoRaffleState(Activity 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{
Activity activity;
public CanRaffleState(Activity activity) {
this.activity = activity;
}
//已经扣过积分了
@Override
public void deductMoney() {
System.out.println("已扣积分。");
}
//可抽奖,抽完后需要更改状态
@Override
public boolean raffle() {
System.out.println("正在抽奖");
int num = new Random().nextInt(10);
if (num == 0){
//num = 0 则抽中了奖,发奖品
activity.setState(activity.getDispenseState());
return true;
}else {
//未抽中奖品
System.out.println("很遗憾未抽到奖品");
activity.setState(activity.getNoRaffleState());
return false;
}
}
@Override
public void dispensePrize() {
System.out.println("很遗憾,未抽中");
}
}
//发放奖品
public class DispenseState extends State{
Activity activity;
public DispenseState(Activity 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.getNoRaffleState());
}else {
System.out.println("奖品已发完");
//状态改变 不能抽奖
activity.setState(activity.getDispenseOutState());
}
}
}
//奖品发完状态
public class DispenseOutState extends State {
//初始化时,传入活动引用
Activity activity;
public DispenseOutState(Activity 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("奖品已发完,请下次再来");
}
}
public class Client {
public static void main(String[] args) {
//定义有20个奖品的活动
Activity activity = new Activity(20);
//遍历30次
for (int i = 1; i <= 30; i++) {
System.out.println("第"+i+"次");
//先扣分后抽奖
activity.deductMoney();
activity.raffle();
}
}
}
状态模式的注意事项和细节
1.代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中
2.方便维护。将容易产生问题的 if else 语句删除了,如果把每个状态的行为都放到一个类中,每次调用方法时都要判断当前是什么状态,不但会产出很多 if else 语句,而且容易出错
3.符合“开闭原则”。容易增删状态
4.会产生很多类。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度
5.应用场景:当 一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状态要求有不同的行为的时 候,可以考虑使用状态模式