当前位置: 首页 > news >正文

设计模式之状态模式详解(State Pattern)

在软件开发过程中,应用程序中的有些对象可能会根据不同的情况做出不同的行为,我们把这种对象称为有状态的对象,而把影响对象行为的一个或多个动态变化的属性称为状态。当有状态的对象与外部事件产生互动时,其内部状态会发生改变,从而使得其行为也随之发生改变。如人的情绪有高兴的时候和伤心的时候,不同的情绪有不同的行为,当然外界也会影响其情绪变化。

对这种有状态的对象编程,传统的解决方案是:将这些所有可能发生的情况全都考虑到,然后使用 if-else 语句来做状态判断,再进行不同情况的处理。但当对象的状态很多时,程序会变得很复杂。而且增加新的状态要添加新的 if-else 语句,这违背了“开闭原则”,不利于程序的扩展。

以上问题如果采用“状态模式”就能很好地得到解决。状态模式的解决思想是:当控制一个对象状态转换的条件表达式过于复杂时,把相关“判断逻辑”提取出来,放到一系列的状态类当中,这样可以把原来复杂的逻辑判断简单化。

认识状态模式

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

状态模式是一种对象行为型模式,其主要优点如下。

  1. 状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
  2. 减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
  3. 有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。

状态模式的主要缺点如下。

  1. 状态模式的使用必然会增加系统的类与对象的个数。
  2. 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。

原理类图与角色说明

状态模式的原理类图

角色说明

  • 环境(Context)角色:也称为上下文,它定义了客户感兴趣的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
  • 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
  • 具体状态(Concrete State)角色:实现抽象状态所对应的行为。

状态模式的使用场景

  • 行为随状态改变而改变的的场景,例如权限设计,人员对象权限不同执行相同的行为其结果也会不同。
  • 条件、分支判断语句替代者,通过扩展子类实现条件判断处理

注意:状态模式适用于当某个对象在它的状态发生改变时,它的行为也随着发生比较大的变化,也就是说在行为受状态约束的情况下可以使用状态模式,而且使用时对象的状态最好不 要超过5个。

应用实例

下面以App抽奖为例运用状态模式:

App抽奖的具体要求如下:

  1. 假如每参加一次这个活动要扣除用户 50 积分,中奖概率是 10%
  2. 奖品数量固定,抽完就不能抽奖
  3. 活动有四个状态: 可以抽奖、不能抽奖、发放奖品和奖品领完
  4. 活动的四个状态转换关系图如下图
    App抽奖状态转移图

抽奖类图

App抽奖类图

代码实现

创建环境角色(Context):RaffleActivity
//管理抽奖状态类
public class RaffleActivity {
    // state 表示活动当前的状态,是变化
    State state = null;
    // 奖品数量
    int count = 0;
    // 四个属性,表示四种状态
    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;
    }

    //扣分, 调用当前状态的 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;
    }


    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;
    }
}
创建抽象状态角色(State)
public interface State {
    //减积分
    public void deductMoney();
    //是否中奖
    public boolean raffle();
    //分发奖品
    public void dispensePrize();
}
创建具体状态角色(Concrete State)
//可以抽奖状态
public class CanRaffleState implements 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) {
            //中奖,改变活动状态为发放奖品
            activity.setState(activity.getDispenseState());
            return true;
        } else {
            System.out.println("很遗憾没有抽中奖品!");
            // 改变状态为不能抽奖
            activity.setState(activity.getNoRafflleState());
            return false;
        }
    }

    /**
     * 不能发放产品
     */
    @Override
    public void dispensePrize() {
        System.out.println("没中奖,不能发放产品");
    }
}
//不能抽奖状态
public class NoRaffleState implements 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("不能发放奖品");
    }
}
//发放奖品状态
public class DispenseState implements 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());
        }
    }
}
//奖品领完状态
public class DispenseOutState implements State {
    RaffleActivity raffleActivity;

    public DispenseOutState(RaffleActivity raffleActivity) {
        this.raffleActivity = raffleActivity;
    }

    @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) {
        // 创建活动对象,奖品有 1 个奖品
        RaffleActivity activity = new RaffleActivity(1);
        // 我们连续抽 20 次奖
        for (int i = 0; i < 20; i++) {
            System.out.println("--------第" + (i + 1) + "次抽奖----------");
            // 参加抽奖,第一步点击扣除积分
            activity.debuctMoney();
            // 第二步抽奖
            activity.raffle();
        }

    }
}
程序运行结果
--------第1次抽奖----------
扣除 50 积分成功,您可以抽奖了
正在抽奖,请稍等!
很遗憾没有抽中奖品!
--------第2次抽奖----------
扣除 50 积分成功,您可以抽奖了
正在抽奖,请稍等!
很遗憾,奖品发送完了
--------第3次抽奖----------
奖品发送完了,请下次再参加
奖品发送完了,请下次再参加
--------第4次抽奖----------
奖品发送完了,请下次再参加
奖品发送完了,请下次再参加
--------第5次抽奖----------
奖品发送完了,请下次再参加
奖品发送完了,请下次再参加
--------第6次抽奖----------
奖品发送完了,请下次再参加
奖品发送完了,请下次再参加
--------第7次抽奖----------
奖品发送完了,请下次再参加
奖品发送完了,请下次再参加
--------第8次抽奖----------
奖品发送完了,请下次再参加
奖品发送完了,请下次再参加
--------第9次抽奖----------
奖品发送完了,请下次再参加
奖品发送完了,请下次再参加
--------第10次抽奖----------
奖品发送完了,请下次再参加
奖品发送完了,请下次再参加
--------第11次抽奖----------
奖品发送完了,请下次再参加
奖品发送完了,请下次再参加
--------第12次抽奖----------
奖品发送完了,请下次再参加
奖品发送完了,请下次再参加
--------第13次抽奖----------
奖品发送完了,请下次再参加
奖品发送完了,请下次再参加
--------第14次抽奖----------
奖品发送完了,请下次再参加
奖品发送完了,请下次再参加
--------第15次抽奖----------
奖品发送完了,请下次再参加
奖品发送完了,请下次再参加
--------第16次抽奖----------
奖品发送完了,请下次再参加
奖品发送完了,请下次再参加
--------第17次抽奖----------
奖品发送完了,请下次再参加
奖品发送完了,请下次再参加
--------第18次抽奖----------
奖品发送完了,请下次再参加
奖品发送完了,请下次再参加
--------第19次抽奖----------
奖品发送完了,请下次再参加
奖品发送完了,请下次再参加
--------第20次抽奖----------
奖品发送完了,请下次再参加
奖品发送完了,请下次再参加

相关文章:

  • 浅谈DataSet 的用法
  • 多维数组和矩阵之顺时针打印二维数组
  • 各种数据库连接代码(JSP)
  • 多维数组与矩阵之0所在的行列清零
  • 多维数组与矩阵之之字形打印矩阵
  • 生姜有味的调色板
  • 设计模式之策略者模式详解(Strategy Pattern)
  • 利用J2ME里的RMS对记录进行排序
  • 设计模式之职责链(责任链)模式(ResponsibilityChain Pattern)
  • SWT GC的drawLine方法的一个隐藏Bug
  • 多维数组与矩阵之子数组的最大累加和
  • 游戏也是软件,J2ME游戏程序员不能忘本
  • 多维数组与矩阵之子矩阵的最大累加和
  • 本周技术关注:Oracle10G、MSSQL2005、MYSQL5: CLuster、Replication、Snapshot
  • 设计模式之解释器模式详解(Interpreter Pattern)
  • 【mysql】环境安装、服务启动、密码设置
  • 【跃迁之路】【519天】程序员高效学习方法论探索系列(实验阶段276-2018.07.09)...
  • Js基础知识(四) - js运行原理与机制
  • learning koa2.x
  • leetcode-27. Remove Element
  • mysql常用命令汇总
  • React系列之 Redux 架构模式
  • Redis字符串类型内部编码剖析
  • uni-app项目数字滚动
  • Vue小说阅读器(仿追书神器)
  • 面试总结JavaScript篇
  • 我的zsh配置, 2019最新方案
  • scrapy中间件源码分析及常用中间件大全
  • ​3ds Max插件CG MAGIC图形板块为您提升线条效率!
  • # Apache SeaTunnel 究竟是什么?
  • #pragma once与条件编译
  • #QT(TCP网络编程-服务端)
  • #微信小程序:微信小程序常见的配置传旨
  • #在 README.md 中生成项目目录结构
  • $refs 、$nextTic、动态组件、name的使用
  • (6)设计一个TimeMap
  • (function(){})()的分步解析
  • (rabbitmq的高级特性)消息可靠性
  • (附源码)计算机毕业设计SSM保险客户管理系统
  • (四)Android布局类型(线性布局LinearLayout)
  • (已解决)什么是vue导航守卫
  • (转)Android学习系列(31)--App自动化之使用Ant编译项目多渠道打包
  • (转)Groupon前传:从10个月的失败作品修改,1个月找到成功
  • *p++,*(p++),*++p,(*p)++区别?
  • .NET 4.0网络开发入门之旅-- 我在“网” 中央(下)
  • .NET CF命令行调试器MDbg入门(一)
  • .NET Core WebAPI中使用Log4net 日志级别分类并记录到数据库
  • .NET Core6.0 MVC+layui+SqlSugar 简单增删改查
  • .net操作Excel出错解决
  • .Net语言中的StringBuilder:入门到精通
  • [100天算法】-不同路径 III(day 73)
  • [1525]字符统计2 (哈希)SDUT
  • [AIGC] Java 和 Kotlin 的区别
  • [BZOJ]4817: [Sdoi2017]树点涂色
  • [C++进阶篇]STL中vector的使用