手机 - 行为模式的实现, 用于描述状态






4.75/5 (10投票s)
2005年4月15日
4分钟阅读

58579

224
一篇关于状态模式的文章,描述了手机的状态行为。
引言
有限状态机 (FSM) 是设计动态行为的重要模型,即接收有限集合中的事件,并跟踪当前状态和前一状态,同时提供一种基于预定事件集合中事件改变状态的机制。该实现是基于时下流行的手机活动,使用 .NET 开发的状态转换系统。
有限状态机
没有 FSM 是一个孤立的过程。FSM 的每个方面都取决于其上下文、需求、编程语言的限制和其他因素。最流行的非面向对象 FSM 实现是链式的 if 语句、嵌套的 switch 语句、状态转换表和使用 goto 语句的代码。它们的流行是因为执行速度快,但可靠性和可维护性较弱。另一方面,纯粹的面向对象 FSM 设计需要“完全具体化”,即使状态机的每个元素都成为一个对象。有限状态机的实现来自“Gang of Four”(GoF) 的设计模式“状态模式”。这是一种面向对象的方法,每个状态和事件都对应一个紧耦合的类结构或类层次结构。
有限状态机至少有两个共同特征
- 第一,存在明确的、有限数量的状态。
- 第二,存在促进状态之间移动的方法,即转换。
状态模式
允许对象在内部状态改变时改变其行为。对象看起来会改变其类。解决方案如下:每个状态都封装在一个实现通用 State 接口 (IState
) 的单独类中,并定义处理所有外部事件的行为。Context
类负责维护当前状态和其他数据的知识。外部实体通过 Context
类与系统通信,该类将请求转发给当前状态对象。使用状态模式时有以下后果。
- 特定于状态的行为被局部化并划分到不同的状态之间。
- 状态转换完全在代码中执行。
- 如果一个对象有很多状态,就需要创建很多具体类,这可能导致类层次结构过于庞大。
Context
类将所有外部事件委托给State
类接口中的相应方法;它负责提供正确的映射。这导致Context
类与具体事件之间紧密耦合。Context
类在哈希表中保留每个状态的一个实例,这样状态更改就不会导致对象的创建或删除。Context
类保留许多状态对象,但一次只有一个是活动的(即响应事件)。
状态
- 空闲状态 - 系统默认处于空闲状态。此外,无论何时取消其他状态,系统都会切换到“空闲”状态。
- 菜单状态 - 系统处于空闲状态。如果我们调用菜单命令,状态将变为菜单状态,操作为“菜单列表”;如果我们调用呼叫命令,状态将变为菜单状态,操作为“上次呼叫列表”。
- 拨号状态 - 系统处于菜单状态。如果我们调用呼叫命令,状态将变为拨号状态,操作为“正在呼叫”,并且在等待一段时间后,如果在此期间我们没有“取消”状态,该呼叫状态将变为“通话中”,操作为“呼叫已连接”。
- 响铃状态 - 系统处于空闲状态。如果我们调用响铃命令,状态将变为响铃状态,操作为“来电”。如果我们调用呼叫命令,状态将变为通话状态,操作为“接听”;如果我们调用取消命令,则在断开连接后状态将变为空闲。
特殊情况
- 如果我们处于空闲状态并调用取消命令,系统会给出消息“已处于空闲状态”。
- 如果我们处于菜单状态并调用菜单命令,系统会给出消息“已处于菜单状态”。
- 如果我们处于拨号/通话状态并调用呼叫命令,系统会给出消息“已处于拨号/通话状态”。
- 如果我们处于通话状态并调用菜单命令,系统会给出消息“操作被忽略:目前处于通话状态”。
- 如果我们处于响铃状态并调用菜单命令,系统会给出消息“操作被忽略:目前处于响铃状态”。
类图
IState 接口
IState
接口是实现实际“智能”的方式。其思想是,一个实体在任何给定时间只能处于一种状态,因此您可以使用此接口来规定进入该状态时会发生什么,何时离开该状态,以及去往何处。
interface IState
{
void menuButton(CellPhone cellPhone);
void callButton(CellPhone cellPhone);
void exitButton(CellPhone cellPhone);
}
CellPhone 类
这很简单地规定了实体必须跟踪当前状态、前一状态,并提供改变状态的机制。我添加了 m_previousState
属性,因为如果代码能够知道它之前处于什么状态,从而能够改变其行为,那么状态机可能会更具通用性。
public class CellPhone
{
private Form1 m_form;
private IState m_previousState;
private IState m_currentState;
private Hashtable m_states = new Hashtable();
public CellPhone(Form1 f)
{
m_states.Add( eState.IDLE, new Idle());
m_states.Add( eState.MENU, new Menu());
m_states.Add( eState.RINGING, new Ringing());
m_states.Add( eState.DIALING, new Dialing());
m_states.Add( eState.TALKING, new Talking());
//default stste is idle
m_currentState = (IState) m_states[eState.IDLE];
m_previousState = null;
m_form = f;
disconnect();
}
public eState getCurrentState()
{
foreach(object key in m_states.Keys)
{
if((IState)m_states[key] == m_currentState)
return (eState)key;
}
return eState.UNKNOWN;
}
public void setCurrentState(eState s)
{
m_previousState = m_currentState;
m_currentState = (IState) m_states[s];
}
//Wrappers to m_currentState
public void menuButton()
{
m_currentState.menuButton( this );
}
public void callButton()
{
m_currentState.callButton( this );
}
public void exitButton()
{
m_currentState.exitButton( this );
}
public void ringButton()
{
setCurrentState( eState.RINGING );
ringing();
}
//More functions
public void call() { Show("Calling..."); }
public void showLastCall() { Show("LastCall List..."); }
public void showMenu() { Show("Menu List..."); }
public void talking() { Show("Call Connected..."); }
public void ringing() { Show("Call is coming..."); }
public void answer() { Show("Answering..."); }
public void disconnect() { Show("Disconnected/Idle..."); }
public void Show(string s)
{
ListViewItem item = new ListViewItem();
item.Text = s;
if(m_previousState != null)
item.SubItems.Add( m_previousState.ToString().Replace("StatePattern.", ""));
else
item.SubItems.Add( "Unknown" );
item.SubItems.Add(m_currentState.ToString().Replace("StatePattern.", ""));
m_form.MainListView.Items.Add( item );
m_form.LState.Text =
m_currentState.ToString().Replace("StatePattern.", "");
m_form.LOperation.Text = s;
}
public void ignore()
{
string str = String.Format(@"Ignored: Currently in {0} state",
m_currentState.ToString());
m_form.MainListView.Items.Add( str.Replace("StatePattern.", "") );
}
public void repeat()
{
string str = String.Format(@"Already in {0} state",
m_currentState.ToString());
m_form.MainListView.Items.Add( str.Replace("StatePattern.", "") );
}
}
具体类
具体类将是 IState
接口的具体实现。请注意,每个类如何实现 IState
接口来定义状态转换到另一个状态时的规则。Alpha
类是一个线程,用于自动将状态从呼叫状态触发到通话状态。
class Idle : IState
{
public void menuButton(CellPhone cellPhone)
{
cellPhone.setCurrentState( eState.MENU );
cellPhone.showMenu();
}
public void callButton(CellPhone cellPhone)
{
cellPhone.setCurrentState( eState.MENU );
cellPhone.showLastCall();
}
public void exitButton(CellPhone cellPhone)
{
cellPhone.repeat();
}
}
class Menu : IState
{
public void menuButton(CellPhone cellPhone)
{
cellPhone.repeat();
}
public void callButton(CellPhone cellPhone)
{
Alpha a = new Alpha( cellPhone );
Thread t = new Thread(new ThreadStart(a.threadProc));
t.Start();
}
public void exitButton(CellPhone cellPhone)
{
cellPhone.setCurrentState( eState.IDLE );
cellPhone.disconnect();
}
}
class Ringing : IState
{
public void menuButton(CellPhone cellPhone)
{
cellPhone.ignore();
}
public void callButton(CellPhone cellPhone)
{
cellPhone.setCurrentState( eState.TALKING );
cellPhone.answer();
}
public void exitButton(CellPhone cellPhone)
{
cellPhone.setCurrentState( eState.IDLE );
cellPhone.disconnect();
}
}
class Dialing : IState
{
public void menuButton(CellPhone cellPhone)
{
cellPhone.ignore();
}
public void callButton(CellPhone cellPhone)
{
cellPhone.repeat();
}
public void exitButton(CellPhone cellPhone)
{
cellPhone.setCurrentState( eState.IDLE );
cellPhone.disconnect();
}
}
class Talking : IState
{
public void menuButton(CellPhone cellPhone)
{
cellPhone.ignore();
}
public void callButton(CellPhone cellPhone)
{
cellPhone.repeat();
}
public void exitButton(CellPhone cellPhone)
{
cellPhone.setCurrentState( eState.IDLE );
cellPhone.disconnect();
}
}
谢谢
我非常感谢 Agha M. Ali 对本文提出的建议。