返回介绍

1.15 状态模式

发布于 2025-01-04 00:44:55 字数 8185 浏览 0 评论 0 收藏 0

状态模式(State Pattern) 采用一种结构化的方法来分割复杂系统中的行为。系统的整体行为会被分成各种定义良好的状态。通常,每个状态由一个独立的类来实现。首先,通过了解当前状态来确定整体系统行为;然后,在该状态中了解可能的行为(体现在对应此状态的类的方法中)。

1.15.1 范例

范例如下:

class Client {
  def context = new Context()
  def connect() {
    context.state.connect()
  }
  def disconnect() {
    context.state.disconnect()
  }
  def send_message(message) {
    context.state.send_message(message)
  }
  def receive_message() {
    context.state.receive_message()
  }
}

class Context {
  def state = new Offline(this)
}

class ClientState {
  def context
  ClientState(context) {
    this.context = context
    inform()
  }
}

class Offline extends ClientState {
  Offline(context) {
    super(context)
  }
  def inform() {
    println "offline"
  }
  def connect() {
    context.state = new Online(context)
  }
  def disconnect() {
    println "error: not connected"
  }
  def send_message(message) {
    println "error: not connected"
  }
  def receive_message() {
    println "error: not connected"
  }
}

class Online extends ClientState {
  Online(context) {
    super(context)
  }
  def inform() {
    println "connected"
  }
  def connect() {
    println "error: already connected"
  }
  def disconnect() {
    context.state = new Offline(context)
  }
  def send_message(message) {
    println "\"$message\" sent"
  }
  def receive_message() {
    println "message received"
  }
}

client = new Client()
client.send_message("Hello")
client.connect()
client.send_message("Hello")
client.connect()
client.receive_message()
client.disconnect()

输出结果如下:

offline
error: not connected
connected
"Hello" sent
error: already connected
message received
offline

Groovy 这样的动态语言中有一个很棒的功能,那就是可以根据我们的需求,利用多种方式来表达该范例。下面来介绍该范例的各种变体形式。

1.15.2 变体 1:利用面向接口的设计

我们可以采取 利用面向接口的设计 的办法,为此引入下面的接口:

interface State {
  def connect()
  def disconnect()
  def send_message(message)
  def receive_message()
}

然后就可以修改 ClientOnlineOffline 类来实现该接口,比如:

class Client implements State {
  // ... as before ...
}

class Online implements State {
  // ... as before ...
}

class Offline implements State {
  // ... as before ...
}

你可能会想:我们不是刚介绍过额外的样板文件代码吗?难道不能依赖 duck-typing 吗?我们可以侥幸使用 duck-typing,但状态模式的一个关键目的在于分割复杂性。如果我们知道 client 类与每一个 state 类都满足一个接口,那么我们就能对复杂性设置一些关键限制。我们就可以独立地查看任何状态类,并且知道与该状态有关的可能行为限制。

其实不必非得使用接口来完成,但它确实能助于表达这种特殊分割方式的意图,并且有助于减少单元测试的尺寸(对于面向接口设计支持得较弱的语言,则必须添加额外的测试来表达这种意图)。

1.15.3 变体 2:抽取状态模式逻辑

作为替代(或者与其他变体相组合),我们可能抽取一部分状态模式逻辑,将其应用到辅助类中。如下例所示,可以在状态模式包/jar/脚本中定义这些类:

abstract class InstanceProvider {
  static def registry = GroovySystem.metaClassRegistry
  static def create(objectClass, param) {
    registry.getMetaClass(objectClass).invokeConstructor([param] as Object[])
  }
}

abstract class Context {
  private context
  protected setContext(context) {
    this.context = context
  }
  def invokeMethod(String name, Object arg) {
    context.invokeMethod(name, arg)
  }
  def startFrom(initialState) {
    setContext(InstanceProvider.create(initialState, this))
  }
}

abstract class State {
  private client

  State(client) { this.client = client }

  def transitionTo(nextState) {
    client.setContext(InstanceProvider.create(nextState, client))
  }
}   

它们都非常具有通用性,可以应用于任何引入状态模式的情况。下面是相应的代码:

class Client extends Context {
  Client() {
    startFrom(Offline)
  }
}

class Offline extends State {
  Offline(client) {
    super(client)
    println "offline"
  }
  def connect() {
    transitionTo(Online)
  }
  def disconnect() {
    println "error: not connected"
  }
  def send_message(message) {
    println "error: not connected"
  }
  def receive_message() {
    println "error: not connected"
  }
}

class Online extends State {
  Online(client) {
    super(client)
    println "connected"
  }
  def connect() {
    println "error: already connected"
  }
  def disconnect() {
    transitionTo(Offline)
  }
  def send_message(message) {
    println "\"$message\" sent"
  }
  def receive_message() {
    println "message received"
  }
}

client = new Client()
client.send_message("Hello")
client.connect()
client.send_message("Hello")
client.connect()
client.receive_message()
client.disconnect()

如上所示,有了 startFromtransitionTo 方法,范例开始有了点 DSL 的感觉。

1.15.4 变体 3:DSL

作为替代(或者与其他变体相组合),我们还可以全面使用领域特定语言(DSL)来处理该例。

定义下面这些通用辅助函数( 首先在此讨论 ):

class Grammar {
  def fsm

  def event
  def fromState
  def toState

  Grammar(a_fsm) {
    fsm = a_fsm
  }

  def on(a_event) {
    event = a_event
    this
  }

  def on(a_event, a_transitioner) {
    on(a_event)
    a_transitioner.delegate = this
    a_transitioner.call()
    this
  }

  def from(a_fromState) {
    fromState = a_fromState
    this
  }

  def to(a_toState) {
    assert a_toState, "Invalid toState: $a_toState"
    toState = a_toState
    fsm.registerTransition(this)
    this
  }

  def isValid() {
    event && fromState && toState
  }

  public String toString() {
    "$event: $fromState=>$toState"
  }
}

class FiniteStateMachine {
  def transitions = [:]

  def initialState
  def currentState

  FiniteStateMachine(a_initialState) {
    assert a_initialState, "You need to provide an initial state"
    initialState = a_initialState
    currentState = a_initialState
  }

  def record() {
    Grammar.newInstance(this)
  }

  def reset() {
    currentState = initialState
  }

  def isState(a_state) {
    currentState == a_state
  }

  def registerTransition(a_grammar) {
    assert a_grammar.isValid(), "Invalid transition ($a_grammar)"
    def transition
    def event = a_grammar.event
    def fromState = a_grammar.fromState
    def toState = a_grammar.toState

    if (!transitions[event]) {
      transitions[event] = [:]
    }

    transition = transitions[event]
    assert !transition[fromState], "Duplicate fromState $fromState for transition $a_grammar"
    transition[fromState] = toState
  }

  def fire(a_event) {
    assert currentState, "Invalid current state '$currentState': passed into constructor"
    assert transitions.containsKey(a_event), "Invalid event '$a_event', should be one of ${transitions.keySet()}"
    def transition = transitions[a_event]
    def nextState = transition[currentState]
    assert nextState, "There is no transition from '$currentState' to any other state"
    currentState = nextState
    currentState
  }
}

现在开始定义并测试状态机:

class StatePatternDslTest extends GroovyTestCase {
  private fsm

  protected void setUp() {
    fsm = FiniteStateMachine.newInstance('offline')
    def recorder = fsm.record()
    recorder.on('connect').from('offline').to('online')
    recorder.on('disconnect').from('online').to('offline')
    recorder.on('send_message').from('online').to('online')
    recorder.on('receive_message').from('online').to('online')
  }

  void testInitialState() {
    assert fsm.isState('offline')
  }

  void testOfflineState() {
    shouldFail{
      fsm.fire('send_message')
    }
    shouldFail{
      fsm.fire('receive_message')
    }
    shouldFail{
      fsm.fire('disconnect')
    }
    assert 'online' == fsm.fire('connect')
  }

  void testOnlineState() {
    fsm.fire('connect')
    fsm.fire('send_message')
    fsm.fire('receive_message')
    shouldFail{
      fsm.fire('connect')
    }
    assert 'offline' == fsm.fire('disconnect')
  }
}

该范例与其他范例并不完全等同,没有用到预先定义的 OnlineOffline 类,而是在需要时定义了整个状态机。关于这一方式更多精心制作的范例,可以参看 这里

也可以参考: 使用 ModelJUnit 的基于模型的测试

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文