一、简单描述
状态模式,它的重点有两个,一是对象的状态的切换,二是对象的状态及其导向的行为
对于一个主体对象,当它处于不同的状态,做同一件事情时,它的行为是不一样的。
二、以用户登录为例
一般来说,用户登录状态有两种:未登录、已登录。不管什么状态,使用者既可能会尝试登录,也可能会尝试注销(登出)。那么,程序需要做出以下反应:
当前为未登录状态:
a、使用者进行登录,程序执行登录
b、使用者进行注销,程序将驳回
当前为已登录状态:
c、使用者进行登录,程序将驳回
d、使用者进行注销,程序执行注销
三、用if判断去实现
一般来说很容易会想到进行各种if...else...判断去完成这项工作
1、初步实现
下面先照着这种想法简单写个用户管理类:
public class UserManage
{
public bool isLoggedIn {//指示登录状态。true表示已登录,false表示未登录
get
{
if (this.Level != null) return true;
else return false;
}
}
public UserManage.AccountLevel? Level { get; set; }//当前账号等级
public void LoginOn(string account,string password)//登录
{
if (this.isLoggedIn)
{
//当前为已登录状态,退出
return;
}
this.Level = verify(account, password);
if (!this.isLoggedIn)
{
//登录失败
}
else
{
//登录成功
}
}
public void LoginOut()//登出
{
if (!this.isLoggedIn)
{
//当前无账号登录,驳回
}
else
{
//执行账号登出
}
}
public UserManage.AccountLevel verify(string account, string password)//账号验证
{
//验证逻辑可根据需要编写
return UserManage.AccountLevel.Top;
}
public enum AccountLevel
{
Bottom,
Top
}
}
2、需求增加
初步实现好像也还行,但是上面只是本地账号登录,甲方可能后期变更需求:
支持MES账号登录,由MES系统进行权限下发和管控。
一般来说MES在线模式一直开启,但是为了应对MES系统故障等情况,软件还需要正常使用,所以还需要保留本地账号登录
为了应对这个需求,
加了以下内容
1、isOnline属性用来指示MES在线状态
2、MESVerify方法用于MES账号验证
3、对LoginOn方法进行了变更,加了些判断
整体如下:
public class UserManage
{
public bool isOnline { get; set; }//指示MES在线状态。true表示在线,false表示离线
public bool isLoggedIn {//指示登录状态。true表示已登录,false表示未登录
get
{
if (this.Level != null) return true;
else return false;
}
}
public UserManage.AccountLevel? Level { get; set; }//当前账号等级
public void LoginOn(string account,string password)//登录
{
if (this.isOnline)
{
if (this.isLoggedIn)
{
//当前为已登录状态,退出
return;
}
this.Level = MESVerify(account, password);
if (!this.isLoggedIn)
{
//登录失败
}
else
{
//登录成功
}
}
else
{
if (this.isLoggedIn)
{
//当前为已登录状态,退出
return;
}
this.Level = verify(account, password);
if (!this.isLoggedIn)
{
//登录失败
}
else
{
//登录成功
}
}
}
public void LoginOut()//登出
{
if (!this.isLoggedIn)
{
//当前无账号登录,驳回
}
else
{
//执行账号登出
}
}
public UserManage.AccountLevel MESVerify(string account, string password)//MES账号验证
{
//验证逻辑可根据需要编写
return UserManage.AccountLevel.Top;
}
public UserManage.AccountLevel verify(string account, string password)//账号验证
{
//验证逻辑可根据需要编写
return UserManage.AccountLevel.Top;
}
public enum AccountLevel
{
Bottom,
Top
}
}
3、总结:
有没有感觉每次增加需求,这个类都会变得更臃肿。但这只是简单的用户登录,如果是一些状态稍微复杂的,那么代码的变更和维护将会变得更加困难。
举个例子,一个游戏人物,有“醒”和“睡眠”两种状态,玩家可能会尝试控制人物进行“跑步”的动作。
按照之前的想法,很简单,用if语句判断后执行对应操作即可。但是增加更多的状态,如:受伤、饥饿等等,这样再使用进行判断语句去执行显得很蠢。
四、剖析“状态模式”
现在回到用户登录的例子。不管是什么状态,用户既可能尝试登录也可能登出(注销)。我们将账户管理对象、账户登录状态、执行的动作这三个分离开来。
1、用户管理对象与状态
用户管理对象有两个状态:
2、状态与动作
每种状态均包含“登录”和“登出”两个动作,只不过执行时只用考虑当前状态就可以,而不用再进行繁琐的if判断
3、状态切换
需要注意执行动作后,对用户管理对象的状态切换
五、用状态模式去实现(简写)
通过拆分把拥有状态的对象、状态、动作三者拆分后,我们可以通过定义接口来规范状态的动作,然后状态类去继承这个接口,并实现其成员(实现动作)即可。
当需要增加新状态时,只需要像已有的状态一样,继承接口并实现其成员即可。
下面来写一下本地账户登录的简单实现,MES登录不在赘述:
1、定义接口
首先定义一个接口,它规定状态对象需要实现的方法(规定需要做的事)。
/*
* 用于人员登录的状态模式
* 1、尝试登录
* 2、尝试注销
*/
/// <summary>定义人员登录动作
///
/// </summary>
public interface IState
{
/// <summary>登录
///
/// </summary>
void Logon(string userName, string password);
/// <summary>注销
///
/// </summary>
void Logout();
}
2、定义状态类
定义已登录状态的类和未登录状态的类,并继承上面的接口
/// <summary>未登录状态</summary>
public class NotLoggedIn:IState
{
private UserSate _useState;
public NotLoggedIn(UserSate userState)
{
this._useState = userState;//对主体对象进行引用
}
/// <summary>尝试登录</summary>
public void Logon(string userName, string password)
{
//进行登录,然后切换为已登录状态。
//如果成功,那就切换主体对象的状态
this._useState.State = this._useState.LoggedIn;
}
/// <summary>尝试注销</summary>
public void Logout()
{
//当前为未登录状态,无法切换状态。不切换状态
}
}
/// <summary>已登录状态</summary>
public class LoggedIn : IState
{
private UserSate _useState;
public LoggedIn(UserSate userState)
{
this._useState = userState;//对主体对象进行引用
}
/// <summary>尝试登录</summary>
public void Logon(string userName, string password)
{
//驳回。不切换状态
}
/// <summary>尝试注销</summary>
public void Logout()
{
//注销。切换为未登录状态
this._useState.State = this._useState.NotLoggedIn;//切换状态
}
}
3、定义用户管理类
/// <summary>用户管理类</summary>
public class UserSate
{
/// <summary>非登录状态</summary>
internal IState NotLoggedIn { get; set; }
/// <summary>已登录状态</summary>
internal IState LoggedIn { get; set; }
/// <summary>当前用户状态</summary>
public IState State { get; set; }
/// <summary>实例化一个用户状态对象</summary>
public UserSate()
{
this.NotLoggedIn = new NotLoggedIn(this);
this.LoggedIn = new LoggedIn(this);
this.State = this.NotLoggedIn;//默认为非登录状态
}
/// <summary>尝试登录</summary>
public void Logon(string userName, string password)
{
this.State.Logon(userName, password);
}
/// <summary>尝试注销</summary>
public void Logout()
{
this.State.Logout();
}
}
六、完整实现
1、接口
namespace StatePattern.CustomerStateInterface
{
/*
* 用于人员登录的状态模式
* 1、尝试登录
* 2、尝试注销
*/
/// <summary>定义人员登录动作
///
/// </summary>
public interface IState
{
/// <summary>登录
///
/// </summary>
StatePattern.UserInfo.UserSate.OperationInfo Logon(string userName, string password);
/// <summary>注销
///
/// </summary>
StatePattern.UserInfo.UserSate.OperationInfo Logout();
}
}
2、状态类
using StatePattern.CustomerStateInterface;
using StatePattern.UserInfo;
namespace StatePattern.State
{
/*
* 用户登录状态
* 1、用户只会尝试2种动作:
* a、尝试登录
* b、尝试注销
* 2、登录状态只会有2种状态
* a、未登录
* b、已登录
*/
/// <summary>未登录状态</summary>
public class NotLoggedIn:IState
{
private UserSate _useState;
public NotLoggedIn(UserSate userState)
{
this._useState = userState;//对主体对象进行引用
}
/// <summary>尝试登录</summary>
public StatePattern.UserInfo.UserSate.OperationInfo Logon(string userName, string password)
{
//进行登录,然后切换为已登录状态
this._useState.UserLevel = ToolClass.Verify(userName, password);
if (this._useState.UserLevel == null)
{
this._useState.UserName = "无";
return new UserSate.OperationInfo() { successful = false, Message = "登录未通过" };
}
else
{
this._useState.State = this._useState.LoggedIn;//切换状态
this._useState.UserName = userName;
return new UserSate.OperationInfo() { successful = true, Message = "登录验证通过" };
}
}
/// <summary>尝试注销</summary>
public StatePattern.UserInfo.UserSate.OperationInfo Logout()
{
//当前为未登录状态,不切换状态
this._useState.UserName = "无";
this._useState.UserLevel = null ;
return new UserSate.OperationInfo() { successful = false, Message = "当前用户未登录" };
}
}
/// <summary>已登录状态</summary>
public class LoggedIn : IState
{
private UserSate _useState;
public LoggedIn(UserSate userState)
{
this._useState = userState;//对主体对象进行引用
}
/// <summary>尝试登录</summary>
public StatePattern.UserInfo.UserSate.OperationInfo Logon(string userName, string password)
{
//驳回。不切换状态
return new UserSate.OperationInfo() { successful = false, Message = "当前已经登录" };
}
/// <summary>尝试注销</summary>
public StatePattern.UserInfo.UserSate.OperationInfo Logout()
{
//注销。切换为未登录状态
this._useState.State = this._useState.NotLoggedIn;//切换状态
this._useState.UserName = "无";
this._useState.UserLevel = null;
return new UserSate.OperationInfo() { successful = true, Message = "注销成功" };
}
}
}
3、用户管理类
using StatePattern.CustomerStateInterface;
using StatePattern.State;
namespace StatePattern.UserInfo
{
/// <summary>用户状态</summary>
public class UserSate
{
/// <summary>非登录状态</summary>
internal IState NotLoggedIn { get; set; }
/// <summary>已登录状态</summary>
internal IState LoggedIn { get; set; }
/// <summary>当前用户状态</summary>
public IState State { get; set; }
/// <summary>是否已登录</summary>
public bool IsLoggedIn
{
get {
if (this.State == this.LoggedIn) return true;
else return false;
}
}
/// <summary>用户名</summary>
public string UserName { get; set; }
/// <summary>用户等级</summary>
public int? UserLevel { get; set; }
/// <summary>实例化一个用户状态对象</summary>
public UserSate()
{
this.NotLoggedIn = new NotLoggedIn(this);
this.LoggedIn = new LoggedIn(this);
this.State = this.NotLoggedIn;//默认为非登录状态
this.UserName = "无";
this.UserLevel = null ;
}
/// <summary>尝试登录</summary>
public OperationInfo Logon(string userName, string password)
{
return this.State.Logon(userName, password);
}
/// <summary>尝试注销</summary>
public OperationInfo Logout()
{
return this.State.Logout();
}
/// <summary>操作消息</summary>
public class OperationInfo
{
/// <summary>操作是否成功</summary>
public bool successful { get; set; }
/// <summary>消息</summary>
public string Message { get; set; }
}
}
}
4、账号验证方法
随便写的,可以自由定义规则
namespace StatePattern
{
public static class ToolClass
{
/// <summary>验证账户是否通过
///
/// </summary>
/// <param name="userName">用户名</param>
/// <param name="password">密码</param>
/// <returns></returns>
public static int? Verify(string userName, string password)
{
//假设只有当用户名为ADMIN、密码为123时,登录验证通过
//level等级为null时表示为未通过验证,1到10表示等级高低,可以定义
if (userName == "ADMIN" && password == "123") return 10;
else return null;
}
}
}
5、简单的调用展示
随便建了个winform程序,就不赘述了,看图。
using StatePattern.UserInfo;
namespace WindowsFormsApplication4_状态机的简单实现_
{
public partial class Form1 : Form
{
public UserSate user;
public Form1()
{
InitializeComponent();
this.user = new UserSate();
this.label_DisplayStatus.Text = string.Format("当前登录用户:{0} 等级:{1}", this.user.UserName, this.user.UserLevel.ToString());
}
private void button_Logon_Click(object sender, EventArgs e)//点击了登录
{
UserSate.OperationInfo info = this.user.Logon(this.textBox_userName.Text, this.textBox_password.Text);
this.label_DisplayStatus.Text = string.Format("当前登录用户:{0} 等级:{1}", this.user.UserName, this.user.UserLevel.ToString());
MessageBox.Show(string.Format("操作是否成功:{0}\r\n操作消息:{1}", info.successful, info.Message));
}
private void button_Logout_Click(object sender, EventArgs e)//点击了注销
{
UserSate.OperationInfo info = this.user.Logout();
this.label_DisplayStatus.Text = string.Format("当前登录用户:{0} 等级:{1}", this.user.UserName, this.user.UserLevel.ToString());
MessageBox.Show(string.Format("操作是否成功:{0}\r\n操作消息:{1}", info.successful, info.Message));
}
}
}
6、效果
未登录状态下,尝试用用户名:ADMIN,密码:123登录
已登录状态下,任意账号登录
已注销的情况下再次点击注销