Python 将转换作为状态机引擎实现状态模式的最佳方法是什么
几年前,我实现了一个带有转换的演示应用程序,并尝试使用状态模式。该实现有4个状态机、43个状态和78个转换,但很难理解,并且没有正确地遵循状态模式。我现在需要执行另一个演示,并一直在重新思考如何执行。下面概述了我提议的方法,我想征求关于如何最好地实现状态模式和转换的想法和意见 维基百科在这里描述了状态模式。我的解释是,特定状态下的行为被封装为一个类,并且所有此类类都具有相同的接口 有两个问题需要解决。首先是如何允许回调的目标依赖于状态,其次是如何拥有到StateAction类的单个接口 我提出的解决这两个问题的方法如下:-Python 将转换作为状态机引擎实现状态模式的最佳方法是什么,python,transition,state-machine,state-pattern,Python,Transition,State Machine,State Pattern,几年前,我实现了一个带有转换的演示应用程序,并尝试使用状态模式。该实现有4个状态机、43个状态和78个转换,但很难理解,并且没有正确地遵循状态模式。我现在需要执行另一个演示,并一直在重新思考如何执行。下面概述了我提议的方法,我想征求关于如何最好地实现状态模式和转换的想法和意见 维基百科在这里描述了状态模式。我的解释是,特定状态下的行为被封装为一个类,并且所有此类类都具有相同的接口 有两个问题需要解决。首先是如何允许回调的目标依赖于状态,其次是如何拥有到StateAction类的单个接口 我提出的
- 将StateAction类的接口定义为单个方法
def dispatch(self、trigger、*args、**kwargs)
- 创建一组StateAction类,每个类对状态中的行为进行编码。(注意这些不是transition.State类)
- 在模型中,在dict中实例化所有这些StateAction类,并键入状态名称
- 跳过
,以便回调调用当前状态下的分派方法,并将触发器作为参数机器。解析\u callable
- 每个实例中的分派方法在self上调用适当的方法
actions=xxx
关键字项来实现的
所有回调都成为对当前状态实例上具有合适触发器参数的dispatch方法的调用。比如说
transitions = [
{ 'trigger': 'melt', 'source': 'solid', 'dest': 'liquid', 'before': 'make_hissing_noises'},
]
将导致调用solid.dispatch('make\u hissing\u noises')
,其中solid是StateAction类的实例
此方法不支持机器附加到模型的各种方便方法。这对我来说不是问题,因为我的应用程序基于事件循环,并且总是使用调用model.trigger(xxx)
来启动状态更改。这对其他人来说可能不是问题,因为如果不同的方法在不同的类上,人们不会期望在模型上有类似的方法
下面的代码用一个简单的示例说明了该方法,其中它为当前状态调用on\u Train\u arrized
方法,即使该方法存在于两个StateAction实例上
from transitions import Machine
from functools import partial
from six import string_types
import logging
logging.basicConfig(level=logging.WARNING)
class StatePatternMachine(Machine):
"""Machine that simplifies using the State Pattern"""
def __init__(self, *args, **kwargs):
self._actions = kwargs.pop('actions')
super().__init__(*args, **kwargs)
@staticmethod
def resolve_callable(target, event_data):
""" Converts a model's property name into a call to dispatch.
The target must be a string.
Args:
target (str): method name
event_data (EventData): Currently processed event
Returns:
callable function dispatch on current state
"""
if not isinstance(target, string_types):
raise ValueError('%r is not be a string' % target)
else:
try:
func = getattr(event_data.machine._actions[event_data.state.name], 'dispatch')
except AttributeError:
raise AttributeError('No dispatch method on %r' % event_data.model.states)
return partial(func, target) # dispatch signature is dispatch(trigger, *args, **kwargs)
class StateAction(object):
"""base class for all classes implementing the State Pattern"""
def dispatch(self, trigger, *args, **kwargs):
"""call the appropriate function on self
this is the common interface to all state classes
"""
func = getattr(self, trigger, None)
if func:
return func(*args, **kwargs)
else:
print(f'have not got {trigger}')
# simple model for a set of states implementing the State Pattern
class BuyingTicket(StateAction):
_name = 'BuyingTicket'
def on_Train_Arrived(self, *args, **kwargs):
print('Missed Train')
def on_Train_Cancelled(self, *args, **kwargs):
print('on_Train_Cancelled')
class Waiting(StateAction):
_name = 'Waiting'
def on_Train_Arrived(self, *args, **kwargs):
print('Caught Train')
def on_Timeout(self, *args, **kwargs):
print('Timeout')
class Home(StateAction):
_name = 'Home'
journey_states = [
BuyingTicket,
Waiting,
Home,
]
transitions = [
{'trigger': 'Train_Arrived', 'source': 'BuyingTicket', 'dest': 'Waiting', 'before': 'on_Train_Arrived'},
{'trigger': 'Train_Cancelled', 'source': 'BuyingTicket', 'dest': 'Home', 'before': 'on_Train_Cancelled'},
{'trigger': 'Train_Arrived', 'source': 'Waiting', 'dest': 'Home', 'before': 'on_Train_Arrived'}
]
class Model(object):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# dict of instances of states
self.states = {s._name: s() for s in journey_states}
self.machine = StatePatternMachine(
model=self,
states=[k for k in self.states],
transitions=transitions,
initial='BuyingTicket',
actions=self.states,
)
model = Model()
model.trigger('Train_Arrived')
Missed Train
True