优雅代码之路-状态模式中如何优雅的切换状态

优雅代码之路-状态模式中如何优雅的切换状态,第1张

优雅代码之路-状态模式中如何优雅的切换状态

目录
  • 简介
  • 使用场景
  • 1. 状态模式类图和简单实现
  • 2. 封装装换逻辑
  • 3. 电梯状态实战
  • 4. 状态机实战
  • 5. 总结
    • 5.1优点
    • 5.2 缺点
    • 5.3 使用场景
    • 5.4注意事项

简介

实例源码地址: 状态模式

状态模式(State Pattern )也称为状态机模式(State Machine Pattern),是允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类,属于行为型模式。

原文:Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.

解释:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。

状态模式中类的行为是状态决定的,不同的状态下有不同的行为。其意图是让一个对象在其内部改变的时候,其行为也随之改变。状态模式核心是状态与行为绑定,不同的状态对应不同的行为。

该模式是行为模式,所以我们的侧重点都应该是他的行为

状态模式与策略模式的区别

原因状态模式策略模式场景不一样侧重在状态的改变侧重在不同的算法依赖关系不一样各种状态存在相互的关系不同策略相互独立改变方不一样只能设置初始状态,在某种行为上自动切换状态用户可自行切换 使用场景

最为常见的就是订单的流转,订单有待支付,待发货,待收货,订单完成等状态,那么在不同的状态有不同的行为,并且在不同的状态下又有哪些行为不可做,好比如订单未支付就不能发货,这里就要做一层判断,未发货就不能收货,这里也要做一层判断,以此类推,代码中就会出现很多if…else或者swich…case

比如电梯状态流转

IStatus.java

//状态接口
public interface IStatus {

    int OPENING_STATE = 1;//敞门状态
    int CLOSING_STATE = 2;//闭门状态
    int RUNNING_STATE = 3;//运行状态
    int STOPPING_STATE = 4;//停止状态

    void open();

    void close();

    void start();

    void stop();
}

ElevatorService.java

public class ElevatorService implements IStatus {

    private int state;

    public void setState(int state) {
        this.state = state;
    }

    @Override
    public void open() {
        //电梯在什么状态下才能关闭
        switch (this.state) {
            case OPENING_STATE://可以关门,同时修改电梯状态
                System.out.println("关门");
                this.setState(CLOSING_STATE);
                break;
            case CLOSING_STATE://电梯是关门状态,则什么都不做
                // do nothing;
                break;
            case RUNNING_STATE://正在运行,门本来就是关闭的,什么都不做
                //do nothing;
                break;
            case STOPPING_STATE://停止状态,门也是关闭的,什么都不做
                //do nothing;
                break;
        }
    }


    @Override
    public void close() {
         //switch代码
    }

    @Override
    public void start() {
        //switch代码
    }

    @Override
    public void stop() {
        //switch代码
    }
}

如上代码,电梯的状态,有开门,关闭、启动,停止四个状态,对应开门,关闭、启动,停止四个动作

电梯在开门状态可以执行关闭动作,但是不能执行启动动作

电梯在关闭状态可以执行开门动作,也可以执行启动动作,甚至可以是停止的动作(关了门但是不按按钮停止在那)

每个状态流转都要做一层if…else或者switch,就会很冗余

状态模式主要解决的就是当控制一个对象状态的条件表达式过于复杂时的情况。通过把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。对象的行为依赖于它的状态(属性),并且会根据它的状态改变而改变它的相关行为。

状态模式适用于以下场景 :

  1. 行为随状态改变而改变的场景;

  2. 一个 *** 作中含有庞大的多分支结构,并且这些分支取决于对象的状态。

1. 状态模式类图和简单实现

从 UML类图中,我们可以看到,状态模式主要包含三种角色:

  1. 环境类角色(Context):定义客户端需要的接口,内部维护一个当前状态实例,并负责
    具体状态的切换

  2. 抽象状态角色(State ):定义该状态下的行为,可以有一个或多个行为;

  3. 具体状态角色(ConcreteState ):具体实现该状态对应的行为,并且在需要的情况下进行状态切换。

-------------------------简单实现-------------------------------

State.java

//抽象状态:State
public interface State {
    void handle();
}

ConcreteStateA.java

//具体状态类
public class ConcreteStateA implements State {
    public void handle() {
        //必要时刻需要进行状态切换
        System.out.println("StateA do action");
    }
}

ConcreteStateB.java

//具体状态类
public class ConcreteStateB implements State {
    public void handle() {
        //必要时刻需要进行状态切换
        System.out.println("StateB do action");
    }
}

Context.java

//环境类
public class Context {
    private static final State STATE_A = new ConcreteStateA();
    private static final State STATE_B = new ConcreteStateB();
    //默认状态A
    private State currentState = STATE_A;

    public void setState(State state) {
        this.currentState = state;
    }

    public void handle() {
        this.currentState.handle();
    }
}

Test.java

public class Test {
    public static void main(String[] args) {
        Context context = new Context();
        context.setState(new ConcreteStateA());
        context.handle();
        context.setState(new ConcreteStateB());
        context.handle();
    }
}

执行结果

StateA do action
StateB do action

ConcreteStateA和ConcreteStateB都实现了State的接口,handle是不同状态的不同行为,在context中初始化状态STATE_A,在test中只要设置不同的State就可以执行不同的行为,这样就不用去关注状态的实现,而是去关注状态的改变

2. 封装装换逻辑

State.java

// 抽象状态:State
public abstract class State {
    protected Context context;

    public void setContext(Context context) {
        this.context = context;
    }

    public abstract void handle();
}

ConcreteStateA.java

// 具体状态类
public class ConcreteStateA extends State {
    @Override
    public void handle() {
        System.out.println("StateA do action");
        // A状态完成后自动切换到B状态
        this.context.setState(Context.STATE_B);
    }
}

ConcreteStateB.java

//具体状态类
public class ConcreteStateB extends State {
    @Override
    public void handle() {
        System.out.println("StateB do action");
    }
}

Context.java

//环境类
public class Context {
    public static final State STATE_A = new ConcreteStateA();
    public static final State STATE_B = new ConcreteStateB();
    // 默认状态A
    private State currentState = STATE_A;
    {
        STATE_A.setContext(this);
        STATE_B.setContext(this);
    }

    public void setState(State state) {
        this.currentState = state;
        this.currentState.setContext(this);
    }

    public State getState() {
        return this.currentState;
    }

    public void handle() {
        this.currentState.handle();
    }
}

Test.java

public class Test {
    public static void main(String[] args) {
        Context context = new Context();
        context.setState(new ConcreteStateA());
        context.handle();
        context.handle();
    }
}

执行结果

StateA do action
StateB do action

对比上一个案例,我们在ConcreteStateA中加入了自动转换逻辑,就好比如支付完状态会自动转换为待发货状态

注:所谓的抽象类是一个抽象的说法,他可以是个接口,也可以是个抽象类

3. 电梯状态实战

ElevatorStatus.java

public abstract class ElevatorStatus {

    protected Context context;

    public void setContext(Context context) {
        this.context = context;
    }

    //开门
    abstract void open();

    //关门
    abstract void close();

    //启动
    abstract void start();

    //停止
    abstract void stop();
}

OpenStatus.java

public class OpenStatus extends ElevatorStatus {

    @Override
    public void open() {
        System.out.println("执行失败:电梯门已打开");
    }

    @Override
    public void close() {
        System.out.println("执行成功:关闭电梯门");
        super.context.setStatusEnum(StatusEnum.CLOSE);
    }

    @Override
    public void start() {
        System.out.println("执行失败:电梯门未关闭");
    }

    @Override
    public void stop() {
        System.out.println("执行失败:电梯未关闭");
    }
}

CloseStatus.java

public class CloseStatus extends ElevatorStatus {


    @Override
    public void open() {
        System.out.println("执行成功:打开电梯门");
        super.context.setStatusEnum(StatusEnum.OPEN);
    }

    @Override
    public void close() {
        System.out.println("执行失败,电梯门已经关闭");
    }

    @Override
    public void start() {
        System.out.println("执行成功:电梯启动");
        super.context.setStatusEnum(StatusEnum.START);
    }

    @Override
    public void stop() {
        System.out.println("执行成功:电梯关门但未启动");
        super.context.setStatusEnum(StatusEnum.STOP);
    }
}

StartStatus.java

public class StartStatus extends ElevatorStatus {

    @Override
    public void open() {
        System.out.println("执行失败:电梯正在运行");
    }

    @Override
    public void close() {
        System.out.println("执行失败:电梯正在运行");
    }

    @Override
    public void start() {
        System.out.println("执行失败:电梯正在运行");
    }

    @Override
    public void stop() {
        System.out.println("执行成功:电梯停止");
        super.context.setStatusEnum(StatusEnum.STOP);
    }
}

StopStatus.java

public class StopStatus extends ElevatorStatus {

    @Override
    public void open() {
        System.out.println("执行成功:打开电梯门");
        super.context.setStatusEnum(StatusEnum.OPEN);
    }

    @Override
    public void close() {
        System.out.println("执行失败:电梯已经关闭");
    }

    @Override
    public void start() {
        System.out.println("执行成功:电梯启动");
        super.context.setStatusEnum(StatusEnum.START);
    }

    @Override
    public void stop() {
        System.out.println("执行失败:电梯已停止");
    }
}

StatusEnum.java

public enum StatusEnum {

    OPEN(new OpenStatus(),"开门"),
    CLOSE(new CloseStatus(),"关门"),
    START(new StartStatus(),"启动"),
    STOP(new StopStatus(),"停止"),;


    private ElevatorStatus status;
    private String name;

    StatusEnum(ElevatorStatus status, String name) {
        this.status = status;
        this.name = name;
    }

    public ElevatorStatus getStatus() {
        return status;
    }

    public String getName() {
        return name;
    }
}

Context.java

public class Context {

    private StatusEnum statusEnum;

    public String getStatus() {
        return "电梯状态:"+statusEnum.getName();
    }


    //初始化状态
    {
        StatusEnum.OPEN.getStatus().setContext(this);
        StatusEnum.CLOSE.getStatus().setContext(this);
        StatusEnum.START.getStatus().setContext(this);
        StatusEnum.STOP.getStatus().setContext(this);
    }

    public void setStatusEnum(StatusEnum statusEnum) {
        this.statusEnum = statusEnum;
    }

    public Context() {
        statusEnum=StatusEnum.CLOSE;
    }

    public void open(){
        statusEnum.getStatus().open();
    }

    public void close(){
        statusEnum.getStatus().close();
    }

    public void start(){
        statusEnum.getStatus().start();
    }

    public void stop(){
        statusEnum.getStatus().stop();
    }
}

Test.java

public class Test {

    public static void main(String[] args) throws InterruptedException {
        Context context=new Context();
        //电梯开门
        context.open();
        System.out.println(context.getStatus());
        TimeUnit.SECONDS.sleep(1);
        //电梯关门
        context.close();
        System.out.println(context.getStatus());
        TimeUnit.SECONDS.sleep(1);
        //电梯启动
        context.start();
        System.out.println(context.getStatus());
        TimeUnit.SECONDS.sleep(1);
        //电梯开门,运行中不能启动
        context.open();
        System.out.println(context.getStatus());
        TimeUnit.SECONDS.sleep(1);
        //电梯停止
        context.stop();
        System.out.println(context.getStatus());
        TimeUnit.SECONDS.sleep(1);
        //电梯开门
        context.open();
        System.out.println(context.getStatus());

    }
}

执行结果

在上面例子中,我们定义了ElevatorStatus电梯状态类,并且规定了开门,关门,启动,停止四个方法

这样每个状态在开门,关门,启动,停止四个动作中执行不同的逻辑并且自动流转状态,而且在不同的状态下什么能做什么不能做也是事先定义好,这样就可以消除了if…else的臃肿

4. 状态机实战

状态机是状态模式的一种应用,相当于上下文角色的一个升级版。在工作流或游戏等各种系 统中有大量使用,如各种工作流引擎,它几乎是状态机的子集和实现,封装状态的变化规则。 Spring也提供给了我们一个很好的解决方案。Spring中的组件名称就叫StateMachine(状态机)。状态机帮助开发者简化状态控制的开发过程,让状态机结构更加层次化。

添加依赖

 
     org.springframework.statemachine
     spring-statemachine-core
     2.0.1.RELEASE
 

OrderStatus.java

public enum OrderStatus {
    // 待支付,待发货,待收货,订单结束
    WAIT_PAYMENT("待支付"),
    WAIT_DELIVER("待发货"),
    WAIT_RECEIVE("待收货"),
    FINISH("订单结束");

    private String name;

    OrderStatus(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

OrderStatusChangeEvent.java

public enum OrderStatusChangeEvent {
    // 支付,发货,确认收货
    PAYED, DELIVERY, RECEIVED;
}

Order.java

public class Order {
    private int id;
    private OrderStatus status;
    public void setStatus(OrderStatus status) {
        this.status = status;
    }

    public OrderStatus getStatus() {
        return status;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    @Override
    public String toString() {
        return "订单号:" + id + ", 订单状态:" + status.getName();
    }
}

OrderStateMachineConfig.java (重点,状态机配置)

@Configuration
@EnableStateMachine(name = "orderStateMachine")
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter {
 
    
    public void configure(StateMachineStateConfigurer states) throws Exception {
        states
                .withStates()
                .initial(OrderStatus.WAIT_PAYMENT)
                .states(EnumSet.allOf(OrderStatus.class));
    }
 
    
    public void configure(StateMachineTransitionConfigurer transitions) throws Exception {
        transitions
                .withExternal().source(OrderStatus.WAIT_PAYMENT).target(OrderStatus.WAIT_DELIVER).event(OrderStatusChangeEvent.PAYED)
                .and()
                .withExternal().source(OrderStatus.WAIT_DELIVER).target(OrderStatus.WAIT_RECEIVE).event(OrderStatusChangeEvent.DELIVERY)
                .and()
                .withExternal().source(OrderStatus.WAIT_RECEIVE).target(OrderStatus.FINISH).event(OrderStatusChangeEvent.RECEIVED);
    }
 
    
    @Bean
    public DefaultStateMachinePersister persister(){
        return new DefaultStateMachinePersister<>(new StateMachinePersist() {
            @Override
            public void write(StateMachineContext context, Order order) throws Exception {
                //此处并没有进行持久化 *** 作
            }
 
            @Override
            public StateMachineContext read(Order order) throws Exception {
                //此处直接获取order中的状态,其实并没有进行持久化读取 *** 作
                return new DefaultStateMachineContext(order.getStatus(), null, null, null);
            }
        });
    }
}

我们可以看到configure有两个重载的方法

我们先来看第一个

public void configure(StateMachineStateConfigurer states)

  • 初始化订单状态,默认未支付状态,是不是跟环境类很相似

public void configure(StateMachineTransitionConfigurer transitions)

  • 配置状态转换事件关系

比如未支付状态(WAIT_PAYMENT)到待发货状态(WAIT_DELIVER)是通过支付(PAY)这个动作

.withExternal().source(OrderStatus.WAIT_PAYMENT).target(OrderStatus.WAIT_DELIVER).event(OrderStatusChangeEvent.PAYED)

下面public DefaultStateMachinePersister persister()看文章注释即可

IOrderService.java

public interface IOrderService {
    //创建新订单
    Order create();
    //发起支付
    Order pay(int id);
    //订单发货
    Order deliver(int id);
    //订单收货
    Order receive(int id);
    //获取所有订单信息
    Map getOrders();
}

OrderServiceImpl.java

@Service("orderService")
public class OrderServiceImpl implements IOrderService {

    @Autowired
    private StateMachine orderStateMachine;
 
    @Autowired
    private StateMachinePersister persister;
 
    private int id = 1;
    private Map orders = new HashMap<>();

    public Order create() {
        Order order = new Order();
        order.setStatus(OrderStatus.WAIT_PAYMENT);
        order.setId(id++);
        orders.put(order.getId(), order);
        return order;
    }

    public Order pay(int id) {
        Order order = orders.get(id);
        System.out.println("线程名称:" + Thread.currentThread().getName() + " 尝试支付,订单号:" + id);
        Message message = MessageBuilder.withPayload(OrderStatusChangeEvent.PAYED).setHeader("order", order).build();
        if (!sendEvent(message, order)) {
            System.out.println("线程名称:" + Thread.currentThread().getName() + " 支付失败,订单号:" + id+",订单状态:"+order.getStatus().getName());
        }
        return orders.get(id);
    }

    public Order deliver(int id) {
        Order order = orders.get(id);
        System.out.println("线程名称:" + Thread.currentThread().getName() + " 尝试发货,订单号:" + id);
        if (!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEvent.DELIVERY).setHeader("order", order).build(), orders.get(id))) {
            System.out.println("线程名称:" + Thread.currentThread().getName() + " 发货失败,订单号:" + id+",订单状态:"+order.getStatus().getName());
        }
        return orders.get(id);
    }

    public Order receive(int id) {
        Order order = orders.get(id);
        System.out.println("线程名称:" + Thread.currentThread().getName() + " 尝试收货,订单号:" + id);
        if (!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEvent.RECEIVED).setHeader("order", order).build(), orders.get(id))) {
            System.out.println("线程名称:" + Thread.currentThread().getName() + " 收货失败,订单号:" + id+",订单状态:"+order.getStatus().getName());
        }
        return orders.get(id);
    }
 

    public Map getOrders() {
        return orders;
    }
 
 
    
    private synchronized boolean sendEvent(Message message, Order order) {
        boolean result = false;
        try {
            orderStateMachine.start();
            //尝试恢复状态机状态
            persister.restore(orderStateMachine, order);
            //添加延迟用于线程安全测试
            Thread.sleep(1000);
            result = orderStateMachine.sendEvent(message);
            //持久化状态机状态
            persister.persist(orderStateMachine, order);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            orderStateMachine.stop();
        }
        return result;
    }

在执行完业务后调用订单动作,订单动作完成进入状态流转

Message message = MessageBuilder.withPayload(OrderStatusChangeEvent.PAYED).setHeader(“order”, order).build();

封装Message,加入订单行为

result = orderStateMachine.sendEvent(message);

调用支付处理完后调用sendEvent,执行状态流转

OrderStateListenerImpl.java

@Component("orderStateListener")
@WithStateMachine(name = "orderStateMachine")
public class OrderStateListenerImpl{
 
    @OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER")
    public boolean payTransition(Message message) {
        Order order = (Order) message.getHeaders().get("order");
        order.setStatus(OrderStatus.WAIT_DELIVER);
        System.out.println("支付成功,状态机反馈信息:" + order.toString());
        return true;
    }
 
    @OnTransition(source = "WAIT_DELIVER", target = "WAIT_RECEIVE")
    public boolean deliverTransition(Message message) {
        Order order = (Order) message.getHeaders().get("order");
        order.setStatus(OrderStatus.WAIT_RECEIVE);
        System.out.println("发货成功,状态机反馈信息:" + order.toString());
        return true;
    }
 
    @OnTransition(source = "WAIT_RECEIVE", target = "FINISH")
    public boolean receiveTransition(Message message){
        Order order = (Order) message.getHeaders().get("order");
        order.setStatus(OrderStatus.FINISH);
        System.out.println("收货成功,状态机反馈信息:" + order.toString());
        return true;
    }
}

行为监听器,比如调用支付(PAY)行为会走payTransition的方法(已在OrderStateMachineConfig.java中配置)

当支付完成后将会在此流转状态

Test.java

@SpringBootApplication
public class Test {
    public static void main(String[] args) {

        ConfigurableApplicationContext context = SpringApplication.run(Test.class, args);

        IOrderService orderService = (IOrderService) context.getBean("orderService");

        List orders = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            orders.add(orderService.create());
        }

        for (int i = 1; i <= orders.size(); i++) {
            int id = i;
            new Thread("客户线程->"+id) {
                @Override
                public void run() {
                    orderService.pay(id);
                    orderService.deliver(id);
                    orderService.receive(id);
                }
            }.start();
        }

    }
}

执行结果

5. 总结 5.1优点
  • 结构清晰
    避免了过多的switch…case或者if…else语句的使用,避免了程序的复杂性,提高系统的可维护性。
  • 遵循设计原则
    很好地体现了开闭原则和单一职责原则,每个状态都是一个子类,你要增加状态就要增加子类,你要修改状态,你只修改一个子类就可以了。
  • 封装性非常好
    这也是状态模式的基本要求,状态变换放置到类的内部来实现,外部的调用不用知道类内部如何实现状态和行为的变换。
5.2 缺点

状态模式既然有优点,那当然有缺点了。但只有一个缺点,子类会太多,也就是类膨胀。如果一个事物有很多个状态也不稀奇,如果完全使用状态模式就会有太多的子类,不好管理,这个需要大家在项目中自己衡量。其实有很多方式可以解决这个状态问题,如在数据库中建立一个状态表,然后根据状态执行相应的 *** 作,这个也不复杂,看大家的习惯和嗜好了。

5.3 使用场景
  • 行为随状态改变而改变的场景
    这也是状态模式的根本出发点,例如权限设计,人员的状态不同即使执行相同的行为结果也会不同,在这种情况下需要考虑使用状态模式。

  • 条件、分支判断语句的替代者
    在程序中大量使用switch语句或者if判断语句会导致程序结构不清晰,逻辑混乱,使用状态模式可以很好地避免这一问题,它通过扩展子类实现了条件的判断处理

5.4注意事项

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

引用:

《设计模式之禅》

《状态模式与备忘录模式详解》

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/4968349.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-13
下一篇 2022-11-13

发表评论

登录后才能评论

评论列表(0条)

保存