【问题标题】:Design Pattern problem involving N states and transitions between them涉及 N 个状态和它们之间的转换的设计模式问题
【发布时间】:2011-01-04 16:35:43
【问题描述】:

我手头有一个问题,我不知道要使用哪种设计模式。 问题是这样的:

我必须构建一个具有“N”个状态的系统,并且我的系统必须根据某些条件从任何状态转换到任何其他状态。 前任: 在条件 1 上,从状态 1 移动到 3,在条件 2 上从状态 1 移动到 4。

即使是从一种状态到另一种状态的转换也可以在 2 个或更多不同的条件下完成。

例如,可以在以下情况下完成从状态 1 到状态 3 的转换:
条件 1:“今天是星期天”
条件 2:“下雨了”
条件 3:“下雨和星期天”
在每种情况下,状态 3 的处理可能不同。

我希望我能够清楚地理解这个问题。请帮忙。

非常感谢

【问题讨论】:

  • 我相信可以通过将状态拆分为多个状态来简化问题,每个状态对应一种处理方式(即,如果从 1 到 3 的转换条件 1 导致处理状态 3 处于不同的状态)从条件 2 开始,然后将状态 3 拆分为两个不同的状态。
  • Petri 网可以处理这种事情。

标签: java design-patterns automata state-machine


【解决方案1】:

状态设计模式适用于状态变化的概念。整个流程生命周期可以分为多个阶段。随着每个阶段的完成,流程从一个状态退出并进入另一个状态。

例如在 JSF 框架中,整个 Web 请求响应生命周期分为六个阶段:

每个阶段完成后,进程从一个状态退出并进入另一个状态。例如,在 RestoreValuePhase 之后,我们可以说 ViewRestored 为退出状态,RequestApply 为进入状态。

所以要实现状态设计模式,需要将整个过程划分为可以在多个阶段进行处理,每个阶段退出定义一个状态变化。

现在让我们通过下面的代码来理解这一点。

任何项目生命周期都可以分为多个阶段,例如

requirementAssessment
Design
Development
QualityAssessment
Deploy
Closure

所以这些是下面示例中使用的阶段

规则:

  1. 我们需要定义一个类,我们可以在其中存储进程的当前状态。下面代码中的 NextPhase 类就是这样做的。

  2. 我们需要定义一个接口,我们可以在其中提供将在每个阶段实现的联系方法。在下面的代码中 ProjectPhase 就是这样做的。

在此处了解有关状态设计模式的更多信息 -- State Design Pattern

http://efectivejava.blogspot.in/2013/09/java-state-design-patten-oops-state.html?utm_source=BP_recent

【讨论】:

    【解决方案2】:

    这显然是一个有限状态机的情况,但最好将条件组合起来,而不是为每个组合创建一个新条件。我不喜欢 Wikipedia 上状态模式的 Java 示例,因为状态知道其他状态,这在很多情况下都没有意义。跟踪from 状态、适用条件to 状态的转换表有助于解决该问题。

    我为面向对象的有限状态机买了两分钱。您可以在 OO 方面进行一些改进,但它可以让想法得到理解。

    class Transition {
        State from;
        Set<Condition> conditions;
        State to;
    }
    
    class State {
        String state;
    }
    
    class Condition {
        String condition;
    }
    

    状态机可以用上述类型构造。没有错误检查,但是如果在某些情况下找不到下一个状态,您可以抛出异常或其他东西。

    class StateMachine {
        List<Transition> transitions;
        State current;
    
        StateMachine(State start, List<Transition> transitions) {
            this.current = start;
            this.transitions = transitions;
        }
    
        void apply(Set<Condition> conditions) {
            current = getNextState(conditions);
        }
    
        State getNextState(Set<Condition> conditions) {
            for(Transition transition : transitions) {
                boolean currentStateMatches = transition.from.equals(current);
                boolean conditionsMatch = transition.conditions.equals(conditions);
                if(currentStateMatches && conditionsMatch) {
                    return transition.to;
                }
            }
            return null;
        }
    }
    

    还有一个测试运行:

    编辑:根据您的评论添加更多转换和新状态:

    State one = new State("one");
    State two = new State("two");
    State three = new State("three");
    
    Condition sunday = new Condition("Sunday");
    Condition raining = new Condition("Raining");
    Condition notSunday = new Condition("Not Sunday");
    Condition notRaining = new Condition("Not Raining");
    
    List<Transition> transitions = new ArrayList<Transition>();
    transitions.add(one, new Set(sunday), three);
    transitions.add(one, new Set(sunday), two); // <<--- Invalid, cant go to two and three
    transitions.add(one, new Set(raining), three);
    transitions.add(one, new Set(sunday, raining), three);
    transitions.add(one, new Set(notSunday, notRaining), three);
    
    StateMachine machine = new StateMachine(one, transitions);
    System.out.print(machine.current); // "one"
    machine.apply(new Set(sunday, raining));
    System.out.print(machine.current); // "three
    

    在一个相当大的项目中使用状态机时,我有过痛苦的经历。问题在于复合状态。就像您提到的复合条件(周日和下雨)一样,技术上可能存在复合状态,可以进一步分解为单位状态。在您的情况下可能会或可能不会出现这种情况,但仍然值得一提。如果是这种情况,最好修改经典的有限状态机并使用 状态集 而不是单个状态来表示 from 和 to 状态。如果你的 N 很大,这将有助于保持理智水平不变。想想 hotmail 文件夹与 gmail 标签。然后转换表将显示为

    Transition(Set<State> from, Set<Condition> conditions, Set<State> to)
    

    【讨论】:

    • 非常感谢 :) 它完全解决了我的目的。您能否详细说明“设置条件”部分并进一步处理它。因此,假设我的情况是“如果 sunday = y 则从 S1 转到 State S2,如果 sunday=n 和 raining=n 则转到 S3 状态,如果 raining=y 则转到 S3 状态”。条件和状态在多个点上纵横交错。非常感谢。
    • 您必须将转换分解为from 状态,并且当应用于此state 时,conditions 的集合会导致to 状态。这也必须是确定性的。所以不可能有两个状态fromconditions 是相似的,但是to 状态是不同的,因为状态机不知道选择哪一个。这是我的代码中的一个错误,因为它总是会选择第一个匹配项,但这将是一个无效的状态机。
    • 也许您需要在getNextState 中返回this.current 而不是null,以免在没有适当的转换时出现错误。没有变化的状态比崩溃更好。
    【解决方案3】:

    正如其他人所说,状态机可以用带有开关的过程代码或带有状态模式的 OO 代码建模,这可能是您所追求的。

    然而,第三种方法是将其实际编码为图形,将状态作为节点,将条件作为有向边。然后可以使用访问者模式将图表应用于不同的用途。这特别适合用户定义状态和/或转换的设计,但可能会比其他答案中描述的硬编码状态机更占用内存。

    【讨论】:

      【解决方案4】:

      我注意到您的示例的第一件事是状态 3 = (状态 1 == true && 状态 2 == true)。随着涉及更多可能的状态,这不会很好地扩展。如果你只考虑是下雨还是星期天,你可以有这样的枚举,有 4 种可能的类型:

      enum State { CLEAR_OTHER_DAY, RAINING_OTHER_DAY, CLEAR_SUNDAY, RAINING_SUNDAY }
      

      这将允许您在编写代码时在 switch 块中清晰地陈述条件。但是,如果您还必须考虑外面是否温暖,则必须在该枚举中再添加 4 个值以捕获所有可能的条件。在您的项目后期,您的代码可能需要捕获比您当前预想的更多的条件。

      对于设计模式,State 模式及其Java example on Wikipedia 看起来是个不错的起点。

      Wikipedia 的示例有一个 StateContext 类和一个名为 setState 的方法,该方法接受一个名称。我考虑过建议您在此处添加状态确定逻辑,但这会使您的StateContext 类需要过于接近其他类的实现细节。最好将一种用于确定系统状态的方法放在一个类中,该类很容易知道从一个状态到另一个状态的条件。这样,如果您的项目将来需要更改,并且您需要跟踪更多状态或不同的条件,您只需将逻辑维护在一个地方。

      【讨论】:

        【解决方案5】:

        这听起来像是 finite state machine 的典型用法

        简而言之,状态机描述了您的系统可以处于的各种状态,以及在哪些条件下它可以从一个状态转到另一个状态。状态机的描述与您的英文描述完全相同。并且可以用state diagrams正式描述

        在代码中,您可以制作这样的状态机:

         enum State { Init, ShowMenu, ShowMsg, DisplayVideo, Exit };
         State state = State.Init;
        
         while (state != State.Exit)
         {
              switch(state)
              {
                   case State.Init:
                        init();
                        state = State.ShowMenu;
                        break;
                   case State.ShowMenu:
                        if(lastMenuItemSelected==1) state = State.ShowMsg;
                        if(lastMenuItemSelected==2) state = State.DisplayVideo;
                        break;
                   case State.ShowMsg:
                        ....
                        break;
                   ....
         }
        

        我不确定我是否得到了正确的 Java 语法...我更喜欢 C#

        【讨论】:

        • 如果我还有点赞(达到当天的配额),你会得到 +1。
        • 解释我的反对意见 - 从面向对象的角度来看,使用 if/switch 不是首选。状态模式是处理状态的更面向对象的方式。
        • 我明确地说:你“可以”制作这样的状态机。我认为我的示例比状态模式的 wiki 解释更容易掌握
        • 它更容易掌握,但这并不意味着它更合适:)
        • @Bozho 没有充分的理由“纯粹”面向对象或“纯粹”功能等。某些问题集使用不同或混合的范式可以更好、更连贯地解决。我会争辩说,如果这个例子更容易掌握,那么以这种方式做某事是一个非常好的理由。说它不好“因为它不是 OO”是一个错误的论点。为什么不是“OO”不好?易于掌握 => 易于维护 => 更快地修复 => 更快、更可靠的产品发布。
        【解决方案6】:

        state pattern 不会做这项工作吗?

        【讨论】:

        • (我添加了关于状态模式的维基百科文章的链接)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-03-02
        • 2010-09-28
        • 2019-06-10
        • 2023-03-07
        相关资源
        最近更新 更多