【设计模式】二十一.行为型模式之状态模式

状态模式

一. 说明

状态模式通常描述一个类不同行为的多个状态变更,对象的行为依赖它的状态,它是一种行为型模式。
状态模式可以用来消除代码中大量的if-else结构,它明确对象是有状态的、对象的不同状态对应的行为不一样、行为之间是可以切换的。简单来讲,就是对象的状态只允许在某个或某些行为下发生改变,否则不允许该行为操作对象状态。

我们用生活中购物订单的例子来说明状态模式,图示如下:
在这里插入图片描述
从图示中可以看出:

  • 未支付的订单只能被用户做支付或者取消操作
  • 已支付的订单只能被用户发货前催单 (按催单对象不同分为两种催单形式)
  • 已发货的订单只能被用户发货后催单
  • 已签收的订单只能被用户评价或删除
  • 已删除的订单用户已经看不到了,无法做任何操作

总的来说就是用户订单的某个状态只能被用户的某个对应行为所改变,而其他行为是无法操作的。

二.应用场景

  1. 线上购物时的订单状态随着不同行为而变化
  2. 营销活动的上线审批流程,活动也是有状态变化的
  3. 游戏时控制的角色在不同环境下有不同的状态(buff加成或debuff时角色属性有不同的变化)

三.代码示例

以购物下单为例,我们在OrderService里实现几个行为:支付、取消订单、催单、判断是否可评价、删除订单这几个行为,按一般的写法是这样的:

先创建OrderService接口,提供几个方法

public interface OrderService {
    /**
     * 订单支付
     */
    boolean pay(OrderDTO orderDTO);
    /**
     * 订单取消
     */
    boolean cancel(OrderDTO orderDTO);
    /**
     * 催单
     */
    boolean reminder(OrderDTO orderDTO);
    /**
     * 订单评价校验
     */
    boolean isEvaluable(OrderDTO orderDTO);
    /**
     * 删除订单
     */
    boolean delete(OrderDTO orderDTO);
}

实现Service接口,每个行为都需要判断在特定状态下才能操作

public class OrderServiceImpl implements OrderService {

    @Override
    public boolean pay(OrderDTO orderDTO) {
        if (orderDTO.getState() == StateEnum.UNPAID) {
            System.out.println("pay-订单支付流程");
            orderDTO.setState(StateEnum.PAID);
            System.out.println("pay-订单支付成功");
            return true;
        }
        System.out.println("pay-该订单无法支付,当前状态为:" + orderDTO.getState().getName());
        return false;
    }
    
    @Override
    public boolean cancel(OrderDTO orderDTO) {
        if (orderDTO.getState() == StateEnum.UNPAID) {
            System.out.println("cancel-订单取消流程");
            orderDTO.setState(StateEnum.CANCEL);
            System.out.println("cancel-订单取消成功");
            return true;
        }
        System.out.println("cancel-该订单无法取消,当前状态为:" + orderDTO.getState().getName());
        return false;
    }
    
    @Override
    public boolean reminder(OrderDTO orderDTO) {
        if (orderDTO.getState() == StateEnum.PAID) {
            System.out.println("reminder-订单催单成功,催单对象为卖家");
            return true;
        }
        if (orderDTO.getState() == StateEnum.DELIVERED) {
            System.out.println("reminder-订单催单成功,催单对象为物流");
            return true;
        }
        System.out.println("reminder-该订单无法催单,当前状态为:" + orderDTO.getState().getName());
        return false;
    }
    
    @Override
    public boolean isEvaluable(OrderDTO orderDTO) {
        if (orderDTO.getState() == StateEnum.SIGNED) {
            System.out.println("evaluable-订单可以评价");
            return true;
        }
        System.out.println("evaluable-该订单无法评价,当前状态为:" + orderDTO.getState().getName());
        return false;
    }
    
    @Override
    public boolean delete(OrderDTO orderDTO) {
        if (orderDTO.getState() == StateEnum.SIGNED) {
            System.out.println("delete-订单删除流程");
            orderDTO.setState(StateEnum.DELETED);
            System.out.println("delete-订单删除成功");
            return true;
        }
        System.out.println("delete-订单无法删除,当前状态为:" + orderDTO.getState().getName());
        return false;
    }
}

编写测试代码

public static void main(String[] args) {
        OrderDTO orderDTO = new OrderDTO();
        orderDTO.setOrderId("202400001");
        orderDTO.setState(StateEnum.UNPAID);

        OrderService service = new OrderServiceImpl();
        service.pay(orderDTO);
        service.pay(orderDTO);

        service.cancel(orderDTO);

        service.reminder(orderDTO);
        orderDTO.setState(StateEnum.DELIVERED);
        service.reminder(orderDTO);

        orderDTO.setState(StateEnum.SIGNED);
        service.isEvaluable(orderDTO);

        service.delete(orderDTO);
    }

在这里插入图片描述

如上图代码所示,这些代码的特点是,OrderService下的每一方法(即行为),都需要考虑订单的各个状态,业务逻辑相对来说复杂一些,如果要增加一个新的订单状态,每个方法都要按需调整,扩展性不高,不符合开闭原则。

我们在SpringBoot环境中使用状态模式重构以上的订单流程:

首先需要引入另一组service,用来表示订单状态的一组业务,命名为OrderStateService,提供与OrderService一致的行为方法

public interface IOrderStateService {

    StateEnum getState();
    /**
     * 订单支付
     */
    boolean pay(OrderDTO orderDTO);
    /**
     * 订单取消
     */
    boolean cancel(OrderDTO orderDTO);
    /**
     * 催单
     */
    boolean reminder(OrderDTO orderDTO);
    /**
     * 订单评价校验
     */
    boolean isEvaluable(OrderDTO orderDTO);
    /**
     * 删除订单
     */
    boolean delete(OrderDTO orderDTO);
}

再编写抽象业务,实现接口的方法,提供默认的实现,默认实现都是该订单状态不支持该行为操作,我们把真正的行为实现落地到具体的实现类上。

@Slf4j
public abstract class AbstractOrderStateService implements IOrderStateService {
    @Override
    public boolean pay(OrderDTO orderDTO) {
        log.info("pay-该订单无法支付,当前状态为:{}", orderDTO.getState().getName());
        return false;
    }

    @Override
    public boolean cancel(OrderDTO orderDTO) {
        log.info("cancel-该订单无法取消,当前状态为:{}", orderDTO.getState().getName());
        return false;
    }

    @Override
    public boolean reminder(OrderDTO orderDTO) {
        log.info("reminder-该订单无法催单,当前状态为:{}", orderDTO.getState().getName());
        return false;
    }

    @Override
    public boolean isEvaluable(OrderDTO orderDTO) {
        log.info("evaluable-该订单无法评价,当前状态为:{}", orderDTO.getState().getName());
        return false;
    }

    @Override
    public boolean delete(OrderDTO orderDTO) {
        log.info("delete-该订单无法删除,当前状态为:{}", orderDTO.getState().getName());
        return false;
    }
}

再重写每个状态对应允许的行为操作,一旦重写即表示该状态允许该行为操作

//未支付状态重写支付、取消、删除方法
@Slf4j
@Service
public class UnpaidOrderStateService extends AbstractOrderStateService {

    @Override
    public StateEnum getState() {
        return StateEnum.UNPAID;
    }

    @Override
    public boolean pay(OrderDTO orderDTO) {
        log.info("pay-订单支付成功,当前订单状态:{}", orderDTO.getState().getName());
        orderDTO.setState(StateEnum.PAID);
        return true;
    }

    @Override
    public boolean cancel(OrderDTO orderDTO) {
        log.info("cancel-订单取消成功,当前订单状态:{}", orderDTO.getState().getName());
        orderDTO.setState(StateEnum.CANCEL);
        return true;
    }

    @Override
    public boolean delete(OrderDTO orderDTO) {
        log.info("delete-订单取消成功,当前订单状态:{}", orderDTO.getState().getName());
        orderDTO.setState(StateEnum.DELETED);
        return true;
    }
}
//已支付状态重写催单方法
@Slf4j
@Service
public class PaidOrderStateService extends AbstractOrderStateService {

    @Override
    public StateEnum getState() {
        return StateEnum.PAID;
    }

    @Override
    public boolean reminder(OrderDTO orderDTO) {
        log.info("发货前催单成功, 催单对象为卖家,当前订单状态:{}", orderDTO.getState().getName());
        return true;
    }
}
//已发货状态重写催单方法
@Slf4j
@Service
public class DeliveredOrderStateService extends AbstractOrderStateService {

    @Override
    public StateEnum getState() {
        return StateEnum.DELIVERED;
    }

    @Override
    public boolean reminder(OrderDTO orderDTO) {
        log.info("发货后催单成功, 催单对象为物流,当前订单状态:{}", orderDTO.getState().getName());
        return true;
    }
}
//已签收状态重写判断评价和删除方法
@Slf4j
@Service
public class SignedOrderStateService extends AbstractOrderStateService {

    @Override
    public StateEnum getState() {
        return StateEnum.SIGNED;
    }

    @Override
    public boolean isEvaluable(OrderDTO orderDTO) {
        log.info("订单允许评价,当前订单状态:{}", orderDTO.getState().getName());
        return true;
    }

    @Override
    public boolean delete(OrderDTO orderDTO) {
        log.info("订单删除成功,当前订单状态:{}", orderDTO.getState().getName());
        orderDTO.setState(StateEnum.DELETED);
        return true;
    }
}
//已删除状态不做任何操作
@Service
public class DeletedOrderStateService extends AbstractOrderStateService {

    @Override
    public StateEnum getState() {
        return StateEnum.DELETED;
    }
}
//已取消状态不做任何操作
@Service
public class CancelOrderStateService extends AbstractOrderStateService {

    @Override
    public StateEnum getState() {
        return StateEnum.CANCEL;
    }
}

编写获取具体StateService的工厂类

@Component
public class OrderStateFactory {

    private final Map<StateEnum, IOrderStateService> stateMap = new HashMap<>();

    @Resource
    private Set<IOrderStateService> orderStateServiceSet;

    @PostConstruct
    public void init() {
        orderStateServiceSet.forEach(service -> {
            stateMap.put(service.getState(), service);
        });
    }

    public IOrderStateService getOrderStateService(StateEnum state) {
        return stateMap.get(state);
    }

}

改造OrderService的实现类

@Service
public class OrderServiceImpl implements OrderService {

    @Resource
    private OrderStateFactory orderStateFactory;

    @Override
    public boolean pay(OrderDTO orderDTO) {
        return orderStateFactory.getOrderStateService(orderDTO.getState()).pay(orderDTO);
    }

    @Override
    public boolean cancel(OrderDTO orderDTO) {
        return orderStateFactory.getOrderStateService(orderDTO.getState()).cancel(orderDTO);
    }

    @Override
    public boolean reminder(OrderDTO orderDTO) {
        return orderStateFactory.getOrderStateService(orderDTO.getState()).reminder(orderDTO);
    }

    @Override
    public boolean isEvaluable(OrderDTO orderDTO) {
        return orderStateFactory.getOrderStateService(orderDTO.getState()).isEvaluable(orderDTO);
    }

    @Override
    public boolean delete(OrderDTO orderDTO) {
        return orderStateFactory.getOrderStateService(orderDTO.getState()).delete(orderDTO);
    }
}

编写测试代码

public static void test(){
        OrderServiceImpl service = (OrderServiceImpl) applicationContext.getBean("orderServiceImpl");

        OrderDTO orderDTO = new OrderDTO();
        orderDTO.setOrderId("202400001");
        orderDTO.setState(StateEnum.UNPAID);

        service.pay(orderDTO);
        service.pay(orderDTO);

        service.cancel(orderDTO);

        service.reminder(orderDTO);
        orderDTO.setState(StateEnum.DELIVERED);
        service.reminder(orderDTO);

        orderDTO.setState(StateEnum.SIGNED);
        service.isEvaluable(orderDTO);

        service.delete(orderDTO);
    }

在这里插入图片描述

可以看到,重构后实现了相同的业务功能,并且完全干掉了if-else的判断代码。后续新增某个订单状态,我们只需要扩展StateService的具体状态实现即可。虽然整个模块的类增加了,略显复杂,但我们牺牲复杂性去换取高可维护性和扩展性是相当值得的。

四. 总结

在状态模式中,我们把每一种状态都独立成一个类进行处理,这满足了单一职责和开闭原则。状态模式强调的是行为和状态的对应,只有在特定的业务场景下使用它,在通常三层架构的web开发中,对象的状态和对象行为一般都是分离的,所以在开发中我们使用地并不多。

状态模式和状态机也是有关系的,状态机类似一种开发引擎便于我们在开发中使用,它同样采用的是状态模式的思想实现的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/289616.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Winform中使用Fleck实现Websocket服务端并读取SQLite数据库中数据定时循环群发消息

场景 Winform中使用Websocket4Net实现Websocket客户端并定时存储接收数据到SQLite中&#xff1a; Winform中使用Websocket4Net实现Websocket客户端并定时存储接收数据到SQLite中-CSDN博客 Winform中操作Sqlite数据增删改查、程序启动时执行创建表初始化操作&#xff1a; Wi…

计算机基础面试题 |08.精选计算机基础面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

前端框架中的状态管理(State Management)

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

【算法每日一练]-dfs bfs(保姆级教程 篇8 )#01迷宫 #血色先锋队 #求先序排列 #取数游戏 #数的划分

目录 今日知识点&#xff1a; 使用并查集映射点&#xff0c;构造迷宫的连通块 vis计时数组要同步当回合的处理 递归求先序排列 基于不相邻的取数问题&#xff1a;dfs回溯 n个相同球放入k个相同盒子&#xff1a;dfs的优化分支暴力 01迷宫 血色先锋队 求先序排列 取数游…

【ikbp】数据可视化DataV

天天查询一些数据&#xff0c;希望来一个托拉拽的展示&#xff0c;部署体验一下可视化大屏 快速搭建快速查询实时更新简单易用 启动服务 数据可视化 静态查询 配置数据 过滤数据 分享

计算机网络—网络搭建NAT内外网映射

使用Windows Server 2003 网络拓扑 Router 外网&#xff1a;NAT IP 网段 192.168.17.0/24内网&#xff1a;仅主机模式 IP 172.16.29.4 Client1&#xff1a;仅主机模式 IP 172.16.29.2 网关 172.16.29.1 Client2&#xff1a;仅主机模式 IP 172.16.29.3 网关 172.16.29.1…

数字信号处理期末复习——计算小题(二)

个人名片&#xff1a; &#x1f981;作者简介&#xff1a;一名喜欢分享和记录学习的在校大学生 &#x1f42f;个人主页&#xff1a;妄北y &#x1f427;个人QQ&#xff1a;2061314755 &#x1f43b;个人邮箱&#xff1a;2061314755qq.com &#x1f989;个人WeChat&#xff1a;V…

uni-app中实现元素拖动

uni-app中实现元素拖动 1、代码示例 <template><movable-area class"music-layout"><movable-view class"img-layout" :x"x" :y"y" direction"all"><img :src"musicDetail.bgUrl" :class&…

SpringBoot实现Websocket聊天交友微信小程序(一)

记录一下我开发一个交友微信小程序并且上线运营的心得体会。 2022年10月1日上线的&#xff0c;到目前终于实现每天收益300左右。 界面比较简洁&#xff0c;功能有动态&#xff0c;动态可以选择话题&#xff0c;相册&#xff0c;相册可以设置看广告解锁&#xff0c;私信&#…

Kubernetes-网络

一. 前言 flannel两种容器跨主机通信的方案&#xff0c;其中UDP模式是IP in UDP&#xff0c;即三层报文封装在UDP数据包中通信&#xff1b;而vxlan模式则是MAC in UDP&#xff0c;即二层报文封装在UDP数据包中通信 flannel UDP模式和vxlan模式都对数据包做了封解包&#xff0c…

59.网游逆向分析与插件开发-游戏增加自动化助手接口-文字资源读取类的C++还原

内容来源于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;游戏菜单文字资源读取的逆向分析-CSDN博客 之前的内容&#xff1a;接管游戏的自动药水设定功能-CSDN博客 码云地址&#xff08;master分支&#xff09;&#xff1a;https://gitee.com/dye_your_fing…

2 Windows网络编程

1 基础概念 1.1 socket概念 Socket 的原意是“插座”&#xff0c;在计算机通信领域&#xff0c;socket 被翻译为“套接字”&#xff0c;它是计算机之间进行通信的一种约定或一种方式。Socket本质上是一个抽象层&#xff0c;它是一组用于网络通信的API&#xff0c;包括了一系列…

2024——剑之所至,所向披靡

目录 *年度总结导航 一.开篇——写在篇头 二.工作篇——心之所向 1.CSDN记录篇 1,1博客主页 1.2 第一篇博文 1.3.产品测试 1.4C站获奖博文 1.5团队创建 2.腾讯云记录篇 2.1博主的主页 2.2 博主好文推荐 2.3腾讯云产品体验 三.励志篇——未来可期 2023年计划 …

苹果cmsV10暗黑大气MT主题模板源码-只有PC版本

苹果cms MT主题是一款多功能苹果cmsV10暗黑大气主题 初次使用说明&#xff1a; 网站模板选择mt 模板目录填写html 后台地址&#xff1a;MT主题,mt/mtset 先应用主题打开前台&#xff0c;再点击后台。 源码下载&#xff1a;https://download.csdn.net/download/m0_66047725…

网络安全—部署CA证书服务器

文章目录 网络拓扑安装步骤安装证书系统安装从属证书服务器 申请与颁发申请证书CA颁发证书 使用windows Server 2003环境 网络拓扑 两台服务器在同一网段即可&#xff0c;即能够互相ping通。 安装步骤 安装证书系统 首先我们对计算机名进行确认&#xff0c;安装了证书系统后我…

阿里云服务器8080端口安全组开通图文教程

阿里云服务器8080端口开放在安全组中放行&#xff0c;Tomcat默认使用8080端口&#xff0c;8080端口也用于www代理服务&#xff0c;阿腾云atengyun.com以8080端口为例来详细说下阿里云服务器8080端口开启教程教程&#xff1a; 阿里云服务器8080端口开启教程 阿里云服务器8080端…

如何在Mendix中实现全文检索

功能背景 在日常的应用使用过程中&#xff0c;存在大量希望使用全文检索技术的场景&#xff0c;对资料库中的内容进行查询。Mendix默认的结构化查询方式&#xff0c;适合对特定业务实体进行类似数据库单表的基于SQL语句的查询。那如何在Mendix实现全文检索的功能呢&#…

13.Go 异常

1、宕机 Go语言的类型系统会在编译时捕获很多错误&#xff0c;但有些错误只能在运行时检查&#xff0c;如数组访问越界、空指针引用等&#xff0c;这些运行时错误会引起宕机。 一般而言&#xff0c;当宕机发生时&#xff0c;程序会中断运行&#xff0c;并立即执行在该gorouti…

rk3588中编译带有ffmpeg的opencv

有朋友有工程需要&#xff0c;将视频写成mp4&#xff0c;当然最简单的方法当然是使用opencv的命令 cv::VideoWriter writer;bool bRet writer.open("./out.mp4", cv::VideoWriter::fourcc(m, p, 4, v), 15, cv::Size(640, 512), 1); 但是奈何很难编译成功&#xff…

go执行静态二进制文件和执行动态库文件

目的和需求&#xff1a;部分go的核心文件不开源&#xff0c;例如验证&#xff0c;主程序核心逻辑等等 第一个想法&#xff0c;把子程序代码打包成静态文件&#xff0c;然后主程序执行 子程序 package mainimport ("fmt""github.com/gogf/gf/v2/os/gfile"…