【浅谈设计模式】(17):状态模式 | 储能充放电实战案例

前言

铁子们,欢迎来到我的设计模式系列文章,今天来讲--状态模式,这是剩余的倒数第六篇设计模式文章了,本文对状态模式进行快速入门,并使用储能充放电的案例实战完成 Demo 演示, 希望可以将它们一网打尽,提升下自己的视野。

一、概述

1.1 引入

状态模式,它来了,它来了,它攀着楼梯跑来了。

言归正传,在阿里的开发手册中推荐,如果超过 3 层的 if-else 的逻辑判断语句就可以状态模式等来实现。

那么何为状态模式呢?

状态模式:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类,其别名为状态对象,状态模式是一种对象行为型模式。

都说 Java 里面万物皆对象,那么每个对象都有一个它本身的状态,比如水的状态有液体、固体、气体,在每种状态下的表现形式不一样,液体可以洗衣服,固体可以砸核桃,气体可以吸欧气~~!

再比如公司小王同学上班写 Bug,周一心情很不好,就写了 10 个 Bug,周二心情非常高兴,进入心流模式,就消除了 10 个 Bug,每天的敲代码的速度和制造 Bug 的数量与他的当前状态表现出不同的行为,且他心情非常容易波动,那这种情况就可以使用状态模式来处理啦。

再举例,在掘金论坛中,根据掘力值进行了多等级的划分,多发表文章可以提升我们的掘力值,不同的掘力值发挥的能力不同,可以对应不同的状态。

例如:

  • LV0 状态:掘力值为 0, 为新手模式,拥有社区基础功能
  • LV1 状态:掘力值小于 40, 为入门模式,具备文章添加投票功能;
  • LV5 状态:当掘力值大于 5500 的时候,就可以拥有优秀创作者称号。
  • ....

就是说,一旦这个对象在其内部状态改变了,就相对应的可以执行它的这个状态行为下的方法。

当相似状态下许多重复代码时候,如果对象需要根据自身状态进行不同行为切换,同时状态的数量非常多,且状态相关的代码频繁变更,状态反复切换的话,我们就可以跳出 if-else 的 舒适区,考虑下今天学习的模式--状态模式。

1.2 相关概念

状态模式包含如下角色:

  • Context(环境类)
    • 环境类也称为上下文类,它是拥有状态的对象,但是由于其状态存在多样性且在不同状态下对象的行为有所不同,因此将状态独立出去形成单独的状态类。
  • State(抽象状态类)
    • 抽象状态类用于定义一个接口封装与环境类的一个特定状态的相关行为,在抽象状态类中声明了各种不同状态对应的方法
  • Concrete(具体状态类)
    • 具体状态类是抽象状态类的子类,每个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。

类图关系如下:

1.3 优缺点

优点:

  • 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
  • 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。

缺点:

  • 状态模式的使用必然会增加系统类和对象的个数。
  • 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
  • 状态模式对"开闭原则"的支持并不太好。

二、储能充放电案例实战

好了,到代码环节了,就以今天非常大火的新能源-储能行业来切入到状态模式中吧,储能就是个充电宝了,三种模式,有时候充电,有时候放电,还有时候待机状态,并且三种状态切换的非常频繁,每个状态下可以表现出不同的行为,那么这种场景下就可以非常恰当的使用状态模式了。

如果用 if-else ,那么代码如下:

scss 复制代码
if(充电状态){
  // 下发充电逻辑
}else if(放电){
  // 下发放电逻辑
}else{
  // 下发待机功率
}

那使用状态模式,我们就要用到上面下的三类角色,并且按照关键步骤实现它们。

实现状态模式的关键步骤:

  • 1、定义状态接口或抽象类:定义一个接口或抽象类,用于表示对象的状态,并在其中声明各种具体状态可能的行为;
  • 2、实现具体状态类:为每个具体状态创建一个类,实现状态接口或抽象类中定义的方法,描述对象在该状态下的具体行为
  • 3、维护状态切换逻辑:在 Context 类中维护对抽象状态类的引用,并且在状态发送变化时切换不同的具体状态。

在补充下我们要实现的需求,储能在自动充放电策略中,SOC 就是它的电量了,类似我们的手机电量百分比,我们在执行下发指令时候,

  • 如果是充电状态,那 SOC 就会慢慢升高,等 SOC 高于一定程度之后就要将它关机
  • 如果是放电状态,那 SOC 就下降, 等 SOC 低于一定程度之后也要将它关机,

所以这个指令下发的时候,对应不同状态是不一样的,同时还有一些方法是只能放电状态调用,一些方法只能充电状态调用,这些表现的行为是不一样的,下面来代码实战。

  • 定义状态抽象类
java 复制代码
public abstract class AbstractState{

    protected static final RuntimeException EXCEPTION = new RuntimeException("本状态下不允许执行此行为");

    // 计算下发功率
    public abstract ChargeCube calcDownPower(ChargeCube chargeCube);

    // 是否需要防逆流(只有放电需要)
    public abstract void isNeedAntiBack(ChargeCube chargeCube);
}
  • 具体状态实现类
arduino 复制代码
public enum ChargeEnum {
    // 充电状态
    ChargeEnum(0, "charge"),
    // 放电状态
    DisCharEnum(1, "disCharge"),
    // 待机状态
    StandbyEnum(2, "standby"),
    ;

    private int key;
    private String value;

    ChargeEnum(int key, String value) {
        this.key = key;
        this.value = value;
    }
    public int getKey() {return key;}
    public String getValue() {return value;}

    }
scala 复制代码
// 充电状态下的具体实现类
public class ChargeConcretState extends AbstractState{

    @Override
    public ChargeCube calcDownPower(ChargeCube chargeCube) {
        chargeCube.setSoc(chargeCube.getSoc() + 10);
        if(chargeCube.getSoc() > 95){
            System.out.println("充电充满了,下发关机指令");
        }else{
            System.out.println("进入充电计算,将SOC值 +10,当前 SOC是:"+chargeCube.getSoc());
        }
        return chargeCube;
    }


    @Override
    public void isNeedAntiBack(ChargeCube chargeCube) {
        throw EXCEPTION;
    }
}
scala 复制代码
放电状态下的实现类
public class DisChargeConcretState extends AbstractState{
    @Override
    public ChargeCube calcDownPower(ChargeCube chargeCube) {
        chargeCube.setSoc(chargeCube.getSoc() - 10);
        if(chargeCube.getSoc() < 5){
            System.out.println("放电放多了,关机指令下发");
        }else{
            System.out.println("进入放电计算,将SOC值 - 10,当前 SOC是:"+chargeCube.getSoc());
        }
        return chargeCube;
    }

    @Override
    public void isNeedAntiBack(ChargeCube chargeCube) {
        System.out.println("进入防逆流状态~");
    }
}
  • 维护状态切换逻辑,上下文
scss 复制代码
public class ChargeContext {
    
    //定义一个当前状态
    private AbstractState chargeState;


    /**
     * 在环境类中定义所有状态执行的方法.
     */

    /**
     * 进行下发值计算,并返回剩余电量
     * @param cube
     */
    public ChargeCube execDownMethod(ChargeCube cube){
        checkStatus(cube);
        return chargeState.calcDownPower(cube);
    }


    /**
     * 是否需要进行防逆流操作(只有放电需要)
     * @param cube
     */
    public void execIsNeedAntiBackMethod(ChargeCube cube){
        chargeState.isNeedAntiBack(cube);
    }


    private void checkStatus(ChargeCube cube) {
        if(cube.getState() == ChargeEnum.ChargeEnum.getKey()){
            this.chargeState = new ChargeConcretState();
        }else if(cube.getState() == ChargeEnum.DisCharEnum.getKey()){
            this.chargeState = new DisChargeConcretState();
        }
    }
}
java 复制代码
@Data
public class ChargeCube {
    private int state; // 充放电状态
    private int soc;// 当前电量

    public ChargeCube(int state, int soc) {
        this.state = state;
        this.soc = soc;
    }
}
  • 测试类
ini 复制代码
public class Client {
    public static void main(String[] args) {
        ChargeContext context = new ChargeContext();
        ChargeCube chargeCube = context.execDownMethod(new ChargeCube(1, 15));
        context.execDownMethod(chargeCube);
        context.execIsNeedAntiBackMethod(chargeCube);

        System.out.println("===========================");

        ChargeCube chargeCube2 = context.execDownMethod(new ChargeCube(0, 80));
        context.execDownMethod(chargeCube2);
        context.execIsNeedAntiBackMethod(chargeCube2);

    }
}

我们只需传入实体类,状态模式识别到具体状态后,就会执行对应状态的方法。

打印结果如下

三、小结

再说下常见的问题,策略模式与状态模式之间的区别,因为这两个除了类名,看起来几乎相同,它们都通过不同的派生子类来更改具体实现。

但是它们的区别大概是,

  • 状态模式:主要是用来处理对象的状态转换,目的是管理对象的状态以及对象的行为,对象的行为会随着状态的变化而变化,状态模式中不同的状态彼此相关,经常是一个状态切换到另一个状态,例如审批工作流也是,订单的完结,未支付等状态,可以具备一个上下文关联关系,状态模式定义了了不同状态下要做什么事情。
  • 策略模式:策略模式主要用来处理算法的选择问题,策略是对一种解决方案的多种算法的实现,它定义了做某件事的多种解决方案,而不是多件事的不同做法,且不同算法之间没有关联关系。

状态模式,又是一个减少 if-else 的杀招,多了解一点,视野就多宽广一点,设计模式系列文章要进入最后五篇的讨伐战了,欢迎点赞收藏~!

相关推荐
码银1 小时前
Java 集合:泛型、Set 集合及其实现类详解
java·开发语言
东阳马生架构1 小时前
Nacos简介—4.Nacos架构和原理
java
柏油1 小时前
MySQL InnoDB 行锁
数据库·后端·mysql
咖啡调调。1 小时前
使用Django框架表单
后端·python·django
白泽talk1 小时前
2个小时1w字| React & Golang 全栈微服务实战
前端·后端·微服务
摆烂工程师1 小时前
全网最详细的5分钟快速申请一个国际 “edu教育邮箱” 的保姆级教程!
前端·后端·程序员
一只叫煤球的猫2 小时前
你真的会用 return 吗?—— 11个值得借鉴的 return 写法
java·后端·代码规范
Asthenia04122 小时前
HTTP调用超时与重试问题分析
后端
东阳马生架构2 小时前
Sentinel源码—8.限流算法和设计模式总结二
算法·设计模式·sentinel
颇有几分姿色2 小时前
Spring Boot 读取配置文件的几种方式
java·spring boot·后端