设计模式实际运用 | 状态模式

 

文章目录


前言

本专栏主要讲述设计模式在实际开发场景中的运用, 如果对设计模式有一定的基础可以更好的理解, 当然, 我会把整个demo完整的贴出来, 尽量注释清楚, 让没有设计模式基础的也可以稍微改改运用在自己的项目中, 这里对设计模式的具体角色构成不多赘余。

状态模式在处理多状态且复杂的业务, 可以把状态变化与业务解耦, 对繁杂的业务梳理处理更简单清晰不容易出错, 多大数项目中都可以运用, 所以本专栏把状态模式放在第一篇


一、状态模式使用场景及优缺点

定义:对有状态的对象,把复杂的判断逻辑提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为

使用场景:
1. 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式
2. 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时

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

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


二、实际开发场景中的运用

1.实体类

这里以订单状态为例:  订单状态有 1待付款  2待派送  3派送中  4已完成  5已取消

具体状态转变代码有注释

package com.practical.behaviorType.state.domain;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;

/**
 * 订单表
 *
 * @TableName orders
 */
@TableName(value = "orders")
@Data
public class Orders implements Serializable {
    /**
     * 主键
     */
    @TableId
    private Long id;
    private String number;
    private Long userId;
    /**
     * 订单状态
     * 1待付款: 可以从 1待付款 -> 5已取消/2待派送
     * 2待派送: 可以从 2待派送 -> 5已取消/3派送中
     * 3派送中: 只能从 3派送中 -> 4已完成
     * 4已完成: 不可操作
     * 5已取消: 不可操作
     */
    private Integer status;
    private Long addressBookId;
    private Date orderTime;
    private Date checkoutTime;
    private Integer payMethod;
    private BigDecimal amount;
    private String remark;
    private String phone;
    private String address;
    private String userName;
    private String consignee;

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
}

2.定义状态类

1. 抽象状态类

package com.practical.behaviorType.state.orderStatus;

import com.practical.behaviorType.state.service.OrdersStatusService;

/**
 * 订单状态 抽象父类
 * <p>
 * 1待付款: 可以从 1待付款 -> 5已取消/2待派送
 * 2待派送: 可以从 2待派送 -> 5已取消/3派送中
 * 3派送中: 只能从 3派送中 -> 4已完成
 * 4已完成: 不可操作
 * 5已取消: 不可操作
 */
public abstract class OrdersStatus {

    //修改订单service(状态模式环境类)
    private OrdersStatusService ordersStatusService;
    //订单状态码
    protected Integer code;

    public void setOrdersStatusService(OrdersStatusService ordersStatusService) {
        this.ordersStatusService = ordersStatusService;
    }

    //强制子类指定各自状态码
    public OrdersStatus(Integer code) {
        this.code = code;
    }

    //付款操作   1待付款 -> 2待派送
    public abstract void pay(Long orderId);

    //派送操作   2待派送 -> 3派送中
    public abstract void send(Long orderId);

    //送达操作   3派送中 -> 4已完成
    public abstract void delivery(Long orderId);

    //取消操作   1待付款/2待派送 -> 5已取消
    public abstract void canceled(Long orderId);

    public Integer getCode() {
        return code;
    }
}

2.具体状态类

状态模式最重要的部分: 在各种状态内部既各状态具体实现类中 编写 各自业务 从 当前状态 执行改变状态方法时 的校验, 修改状态等操作, 下面只是一个简单的例子, 大家自行根据业务编写校验逻辑

package com.practical.behaviorType.state.orderStatus;

import com.practical.behaviorType.state.constant.OrdersStatusConstant;
import com.practical.behaviorType.state.domain.Orders;
import com.practical.behaviorType.state.service.OrdersService;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * 待支付状态: 1
 *
 * @author zlj
 * @since 2023/8/10
 */
@Component
public class ObligationStatus extends OrdersStatus {

    @Resource
    private OrdersService ordersService;

    //指定状态码
    public ObligationStatus() {
        super(1);
    }

    @Override
    public void pay(Long orderId) {
        //修改数据库状态为待派送
        ordersService.lambdaUpdate()
                .set(Orders::getStatus, OrdersStatusConstant.WAITING_STATUS.code)
                .eq(Orders::getId, orderId)
                .update();
        //模拟
        System.out.println("订单: " + orderId + ", 开始派送");
    }

    @Override
    public void send(Long orderId) {
        throw new RuntimeException("此订单还未支付, 不可派送");
    }

    @Override
    public void delivery(Long orderId) {
        throw new RuntimeException("此订单还未支付, 不可送达");
    }

    @Override
    public void canceled(Long orderId) {
        //修改数据库状态为取消订单
        ordersService.lambdaUpdate()
                .set(Orders::getStatus, OrdersStatusConstant.CANCELED_STATUS.code)
                .eq(Orders::getId, orderId)
                .update();
        //模拟
        System.out.println("订单: " + orderId + ", 已取消");
    }
}
package com.practical.behaviorType.state.orderStatus;

import com.practical.behaviorType.state.constant.OrdersStatusConstant;
import com.practical.behaviorType.state.domain.Orders;
import com.practical.behaviorType.state.service.OrdersService;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * 待派送状态 2
 *
 * @author zlj
 * @since 2023/8/10
 */
@Component
public class WaitingStatus extends OrdersStatus {

    @Resource
    private OrdersService ordersService;

    public WaitingStatus() {
        super(2);
    }


    @Override
    public void pay(Long orderId) {
        throw new RuntimeException("此订单已支付, 不可重复支付");
    }

    @Override
    public void send(Long orderId) {
        //修改数据库状态为3派送中
        ordersService.lambdaUpdate()
                .set(Orders::getStatus, OrdersStatusConstant.SENDING_STATUS.code)
                .eq(Orders::getId, orderId)
                .update();
        //模拟
        System.out.println("订单: " + orderId + ", 开始派送");
    }

    @Override
    public void delivery(Long orderId) {
        throw new RuntimeException("此订单还未派送, 不能完成订单");
    }

    @Override
    public void canceled(Long orderId) {
        //修改数据库状态为取消订单
        ordersService.lambdaUpdate()
                .set(Orders::getStatus, OrdersStatusConstant.CANCELED_STATUS.code)
                .eq(Orders::getId, orderId)
                .update();
        //模拟
        System.out.println("订单: " + orderId + ", 已取消");
    }
}
package com.practical.behaviorType.state.orderStatus;

import com.practical.behaviorType.state.constant.OrdersStatusConstant;
import com.practical.behaviorType.state.domain.Orders;
import com.practical.behaviorType.state.service.OrdersService;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * 派送中状态 3
 *
 * @author zlj
 * @since 2023/8/10
 */
@Component
public class SendingStatus extends OrdersStatus {
    @Resource
    private OrdersService ordersService;

    public SendingStatus() {
        super(3);
    }

    @Override
    public void pay(Long orderId) {
        throw new RuntimeException("此订单已支付, 不可重复支付");
    }

    @Override
    public void send(Long orderId) {
        throw new RuntimeException("此订单正在派送");
    }

    @Override
    public void delivery(Long orderId) {
        //修改数据库状态为已完成
        ordersService.lambdaUpdate()
                .set(Orders::getStatus, OrdersStatusConstant.FINISH_STATUS.code)
                .eq(Orders::getId, orderId)
                .update();
        //模拟
        System.out.println("订单: " + orderId + ", 已完成");
    }

    @Override
    public void canceled(Long orderId) {
        throw new RuntimeException("此订单正在派送, 不可取消订单");
    }
}
package com.practical.behaviorType.state.orderStatus;

import com.practical.behaviorType.state.service.OrdersService;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * 已完成状态 4
 *
 * @author zlj
 * @since 2023/8/10
 */
@Component
public class FinishStatus extends OrdersStatus {
    @Resource
    private OrdersService ordersService;

    public FinishStatus() {
        super(4);
    }

    @Override
    public void pay(Long orderId) {
        throw new RuntimeException("此订单已完成, 不可操作");
    }

    @Override
    public void send(Long orderId) {
        throw new RuntimeException("此订单已完成, 不可操作");
    }

    @Override
    public void delivery(Long orderId) {
        throw new RuntimeException("此订单已完成, 不可操作");
    }

    @Override
    public void canceled(Long orderId) {
        throw new RuntimeException("此订单已完成, 不可操作");
    }


}
package com.practical.behaviorType.state.orderStatus;

import com.practical.behaviorType.state.service.OrdersService;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * 已取消状态 5
 *
 * @author zlj
 * @since 2023/8/10
 */
@Component
public class CanceledStatus extends OrdersStatus {
    @Resource
    private OrdersService ordersService;

    public CanceledStatus() {
        super(5);
    }

    @Override
    public void pay(Long orderId) {
        throw new RuntimeException("此订单已取消, 不可操作");
    }

    @Override
    public void send(Long orderId) {
        throw new RuntimeException("此订单已取消, 不可操作");
    }

    @Override
    public void delivery(Long orderId) {
        throw new RuntimeException("此订单已取消, 不可操作");
    }

    @Override
    public void canceled(Long orderId) {
        throw new RuntimeException("此订单已取消, 不可操作");
    }
}

3.定义状态常量

这里用了hutool的工具类SpringUtil.getBean()从springboot中获取bean

package com.practical.behaviorType.state.constant;

import cn.hutool.extra.spring.SpringUtil;
import com.practical.behaviorType.state.orderStatus.*;

/**
 * 状态常量
 *
 * @author zlj
 * @since 2023/8/10
 */
public interface OrdersStatusConstant {

    //1待付款
    ObligationStatus OBLIGATION_STATUS = SpringUtil.getBean(ObligationStatus.class);
    //2待派送
    WaitingStatus WAITING_STATUS = SpringUtil.getBean(WaitingStatus.class);
    //3派送中
    SendingStatus SENDING_STATUS = SpringUtil.getBean(SendingStatus.class);
    //4已完成
    FinishStatus FINISH_STATUS = SpringUtil.getBean(FinishStatus.class);
    //5已取消
    CanceledStatus CANCELED_STATUS = SpringUtil.getBean(CanceledStatus.class);
}

4.定义修改状态Service(状态模式环境类)

package com.practical.behaviorType.state.service;

import com.practical.behaviorType.state.domain.Orders;
import com.practical.behaviorType.state.orderStatus.OrdersStatus;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 状态模式环境类: 修改状态Service
 */
@Service
public class OrdersStatusService {
    //用于存储所有状态, key: 状态值code  value: 具体状态类的bean
    private final static Map<Integer, OrdersStatus> map = new HashMap<>();

    @Resource
    private OrdersService ordersService;//用于操作数据库订单表
    @Resource
    private List<OrdersStatus> ordersStatusList;//注入所有OrdersStatus子类的bean

    //在bean初始化后将所有具体状态放入map
    @PostConstruct
    private void init() {
        for (OrdersStatus ordersStatus : ordersStatusList) {
            map.put(ordersStatus.getCode(), ordersStatus);
        }
    }

    //当前状态
    private OrdersStatus ordersStatus;

    private void setOrdersStatus(Integer status) {
        this.ordersStatus = map.get(status);//设置环境的当前状态
        this.ordersStatus.setOrdersStatusService(this);//指定状态类的环境
    }

    //从数据库查询当前状态
    private Integer getStatus(Long orderId) {
        return ordersService.lambdaQuery().select(Orders::getStatus).eq(Orders::getId, orderId).one().getStatus();
    }

    //付款操作   1待付款 -> 2待派送
    public void pay(Long orderId) {
        this.setOrdersStatus(this.getStatus(orderId));
        ordersStatus.pay(orderId);
    }

    //派送操作   2待派送 -> 3派送中
    public void send(Long orderId) {
        this.setOrdersStatus(this.getStatus(orderId));
        ordersStatus.send(orderId);
    }

    //送达操作   3派送中 -> 4已完成
    public void delivery(Long orderId) {
        this.setOrdersStatus(this.getStatus(orderId));
        ordersStatus.delivery(orderId);
    }

    //取消操作   1待付款/2待派送 -> 5已取消
    public void canceled(Long orderId) {
        this.setOrdersStatus(this.getStatus(orderId));
        ordersStatus.canceled(orderId);
    }
}

5.测试

76af36a0c17548b1a15d8b8b39526f23.png

 完全按照预期执行, 状态模式完成


总结

喜欢的朋友多多收藏点赞,如果对设计模式的实际运用感兴趣的人多, 我会继续更新更多的模式, 大家可以在评论区留言希望下一篇文章更新什么状态模式, 我从中挑取大家留言最多的模式优先码字, 欢迎留言。

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值