状态模式

状态模式

摘要

本文以 Java 和 C# 示例说明状态模式的概念和应用场景。

定义

状态模式(state pattern)允许对象在内部状态改变时改变它的行为, 对象看起来好像修改了它的类。

状态模式

解读

  • 行为根据状态改变,不同的状态有不同的行为

场景举例

  • 自动贩卖机
  • 家用电表
  • ...

状态模式和策略模式

  • 策略模式围绕可以互换的算法进行业务操作
  • 状态模式通过改变对象的内部状态来帮助对象控制自己的行为
  • 状态模式将各个状态所对应的操作分离开来,即对于不同的状态,由不同的子类实现具体操作,不同状态的切换由子类实现,当发现传入参数不是自己这个状态所对应的参数,则自己给Context类切换状态;而策略模式是直接依赖注入到Context类的参数进行选择策略,不存在切换状态(取自知乎网友的回答)

下面用代码示例进行说明:

代码示例

Java代码


// 状态接口
public interface ManState {
    String NextBehavior();
}

// 状态-早上醒来
public class Wakeup implements ManState {
    @Override
    public String NextBehavior() {
        return "正在起床";
    }
}

// 状态-吃早餐
public class Eatting implements ManState {
    @Override
    public String NextBehavior() {
        return "正在吃早饭";
    }
}

// 状态-上班
public class Working implements ManState{
    @Override
    public String NextBehavior() {
        return "正在工作";
    }
}

// 状态-睡觉
public class Sleeping implements ManState{
    @Override
    public String NextBehavior() {
        return "正在睡觉";
    }
}

// 上下文 - Man
public class Man {

    private ManState state;

    public Man(){
        state = null;
    }

    // 设置状态
    public void setState(ManState state){
        this.state = state;
    }

    // 获取状态
    public ManState getState(){
        return state;
    }
}

public static void main(String[] args) throws Exception {
        
        //定义上下文
        Man man = new Man();

        // 醒了
        ManState state = new Wakeup();

        this.man.setState(state);
        this.man.getState().NextBehavior();

        // 吃饭
        state = new Eatting();

        this.man.setState(state);
        this.man.getState().NextBehavior();

        // 上班
        state = new Working();

        this.man.setState(state);
        this.man.getState().NextBehavior();

        //睡觉
        state = new Sleeping();

        this.man.setState(state);
        this.man.getState().NextBehavior();
}

上面的例子,以员工为例,说明一天中做的事情,其状态是在外部进行控制的,事实上,根据需求,也可以在内部实现状态的转换,这就需要将上下文注入到具体状态的实现中,以实现一个类似指针的效果。

public interface EmpState {
    void DoSomething(Employee emp);
}

public class Wakeup implements EmpState {
    @Override
    public void DoSomething(Employee emp) {
        System.out.println("起床了");
        emp.SetState(new Eatting());
    }
}

public class Eatting implements  EmpState {
    @Override
    public void DoSomething(Employee emp) {
        System.out.println("吃早饭呢");
        emp.SetState(new Working());
    }
}

public class Working implements EmpState {
    @Override
    public void DoSomething(Employee emp) {
        System.out.println("正在工作");
        emp.SetState(new Sleeping());
    }
}

public class Sleeping implements EmpState {
    @Override
    public void DoSomething(Employee emp) {
        System.out.println("正在睡觉");
        System.out.println("没有其他状态了");
    }
}

public class JavaStateDemo {

    public static void main(String[] args) {

        EmpState state = new Wakeup();

        //需要一个初始状态
        Employee emp = new Employee(state);

        emp.Go();
        emp.Go();
        emp.Go();
        emp.Go();
    }
}

这个例子从内部改变Employee的状态,以实现不同的行为。

员工请假/工作天数计算示例

设想有以下场景:需求方连续请假和非连续请假的处理措施是不同的,需要清楚的知道员工每个月的工作和请假情况,我们实现如下的状态操作模型

using System;

namespace StatePatternDemo
{
    // 抽象状态接口
    public interface IWorkState
    {
        void HandleWorkState(Employee emp);
    }

    // 请假
    public class Leaving:IWorkState
    {
        public void HandleWorkState(Employee emp)
        {
            System.Console.WriteLine($"{emp.Name} 请假 {emp.LeavingDays} 天");
            System.Console.WriteLine($"{emp.Name} 上次请假日期是 {emp.LastLeavingDate} ");
        }
    }

    // 工作
    public class Working:IWorkState
    {
        public void HandleWorkState(Employee emp)
        {
            System.Console.WriteLine($"{emp.Name} 正常上班中...");
            System.Console.WriteLine($"{emp.Name} 已连续工作 {Datetime.Now.Day - LastLeavingDate} 天");
        }

    }

    // 上下文
    public class Employee
    {
        // 职员姓名(标识)
        public string Name { get; set; }

        public double LeavingDays { get; set; } // 本次请假天数
        public Datetime LastLeavingDate { get; set; } // 上次请假日期
        public double TotalLeavingDays { get; set; } // 共请假天数

        // 工作天数
        public double WorkingDays { get; set; }

        //公休天数
        private int restDays { get; set;}

        private IWorkState WorkState;

        public Employee(IWorkState workState)
        {
            this.WorkState = workState;
        }

        public void SetState(IWorkState workState)
        {
            this.WorkState = workState;
        }

        /// <code>
        /// 上班签到或请假导致的状态改变
        /// </code>
        public void WorkingStateChanging(Employee emp)
        {
            this.WorkState.HandleWorkState(Employee emp);
        }
    }
}

如上代码中,我把上下文类变成了Employee对象,代表一个职员,这个职员有两种不同的状态,其中请假状态还可以细分为连续请假和非连续请假,也就是说,如果代码继续细化,我们还可以在该状态模式中再嵌套一个状态模式,以更清楚的描述需求。


namespace StatePatternDemo
{
    public interface ILeavingState
    {
        void Leaving(Employee emp);
    }


    public class NotContinusLeaving:ILeavingState
    {
        public void Leaving(Employee emp)
        {
            //非连续请假的实现逻辑
        }
    }

    public class ContinusLeaving:ILeavingState
    {
        public void Leaving(Employee emp)
        {
            //非连续请假的实现逻辑
        }
    }

    // 其他逻辑
}

小结:

  • 状态模式避免了代码中繁复的if/else/switch逻辑,使代码更容易阅读和维护
  • 将状态引起的变化独立封装在各自的实现中,所以它符合开闭原则

以上就是我对状态模式的理解,认为我理解的有问题的,可以在下面留言,谢谢大家!