小弟我的处女作——设计模式之状态模式
从注册博客的那天起,我的职业生涯开始了漫长的求索过程。一点一滴的成长离不开博客园的滋润。和很多人一样,每天上班开始不是敲代码而是打开博客园看看技术文章,每次遇到问题博客园走起。伴随我1000多天的岁月,看看身边人都在写博客,我也抛开我闷骚的性格,套上猪一样的脸皮,来这里分享下我工作中使用的设计模式,觉得有什么问题的可以留言或者直接Q我,大家在博客园的平台上一起成长。
言归正传,可能大家对状态模式会有以下疑问:
什么是状态模式?
什么时候用到状态模式?
状态模式有什么好处?
如何实现状态模式?
接下来我通过我的亲身亲历来回答以上几个问题?
一:什么是状态模式
状态模式:顾名思义是对状态变化的一个封装。状态模式就是当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类[DP]。注:摘抄自大话设计模式
其实在工作中咱们或多或少都接触到状态变更的需求,只不过咱们对状态的变化就是通过if else 或 swicth来判断 比如 当状态等于1的时候更改状态为2,
状态模式是通过子类继承父类后,根据子类是否重写父类状态的虚函数来限制是否允许状态变更到下一个状态的过程。注:这样可以防止状态随意改动,只有重写父类虚函数的状态子类才能变更到下一个指定的状态。
二:什么时候用到状态模式
当一个状态的变更由上一个状态决定时,应该使用状态模式。
例如:我有一个订单,订单状态有未提交,待审核,审核通过,审核失败四个状态,
订单的变化应该是:点击提交订单时,订单状态由未提交变成待审核;点击审核(待审核的订单)订单,可以变成审核通过或审核失败。
此种情景使用状态模式最为合适。
三:状态模式的好处
1: 消除庞大的条件分支判断
2: 减少状态类之间的相互依赖
3:维护方便,我可以根据需求变更随笔更改状态的变更规则,而不用修改其他代码。
4:体现开闭原则和单一原则。
5:安全。这点可能很多人会纳闷,其实刚刚接触的时候我也不太懂,在工作中用了以后领悟到了这点。这点我会在类图和代码中说明一下。
四:如何使用状态模式
现在我做的一个功能是,货主提交订单的一个功能,货主订单有待提交(保存后未提交的)状态,提交后需要管理员审核,审核通过更改状态为审核通过,失败更改为审核失败。在这里我简单列出来这几个状态用来说明状态模式(因为我是做物流平台的,订单状态总共有30多种,所以我才想到了状态模式,要不然30多种,你做swicth判断要100行代码吧,多个地方都这样去判断的话是不是就成了成堆的垃圾代码了,万一我状态变更有新需求的话维护起来更是不言而喻的大坑啊)。
接下来我会把我用到的状态模式写的类图和代码一一列出来
状态模式类图如下:
类图介绍:
StateHelper类:
属性:UserId 当前操作的用户id,OrderId 当前的订单Id,State 当前的状态值,_orderState 状态父类 (通过里氏替换原则,实例化具体的子类)
方法:构造方法 OrderStateHelper 初始化属性操作
SetState() 方法在构造方法里被调用,用来给_orderState 实例化具体的子类。此方法用privite来修饰
ChangState()方法是 改变订单状态为其他状态 在调用状态模式的时候调用
OrderState类:
UpdateState() 更新订单状态方法
PendingSubmission(),PendingAudit等是子类根据需要是否需要重写的操作类
PendingSubmissionState(待提交状态)类
继承OrderState类,待提交状态可以变成待审核状态,所以重写PendingAudit(待审核)方法
PendingAuditState(待审核状态)类
继承OrderState类,待审核状态可以变成审核通过、审核不通过,所以重写AuditNotPass(审核通过)和AuditPass(审核不通过)方法
AuditPassState(审核通过状态)类和AuditNotPassState(审核不通过状态)类
继承OrderState类,由于审核通过和审核不通过状态已经结束,所以不重写任何方法
OrderStateEnum(订单状态枚举)类
这里我重点说明一下 枚举状态的名字和类名去掉State后缀一样,保持这个规则,方便用反射实现订单状态的变更
以下是源代码
StateHelper类
namespace Solution.Logic.Order.OrderState
{
public class OrderStateHelper
{
/// <summary> /// 用户Id /// </summary> public long UserId { get; set; } /// <summary> /// 订单id /// </summary> public int OrderId { get; set; } /// <summary> /// 订单父类 /// </summary> public OrderState _orderState { get; set; } public OrderStateHelper(long userId, string orderId, int state) { UserId = userId; OrderId = orderId; SetState(state); } #region 设置当前订单状态类(实例化子类) /// <summary> /// 设置当前订单状态 /// </summary> /// <param name="value">当前订单状态值</param> public void SetState(int value) { //设置当前枚举值 _enumValue = EnumHelper.GetInstance<OrderStateEnum>(value); #region 通过命名空间+类名 实例化具体的子类 string adaptorName = this.GetType().Namespace + "." + _enumValue+ "State"; var adaptorType = Type.GetType(adaptorName); if (adaptorType != null) { _orderState = Activator.CreateInstance(Type.GetType(adaptorName), null) as OrderState; } #endregion } #endregion #region 改变订单状态为其他状态 /// <summary> /// 改变订单状态为其他状态 /// </summary> /// <param name="value">新的订单状态值</param> public bool ChangState(int value) { //设置当前枚举值 _enumValue = EnumHelper.GetInstance<OrderStateEnum>(value); #region 通过反射方法名字符串动态调用方法 Type type = _orderState.GetType(); var method = type.GetMethod(_enumValue.ToString()); return (bool)method.Invoke(_orderState, new object[] { this }); #endregion } #endregion
}
}
下面是需要注意的一些规则:
OrderState类:
所有状态类的父类,下面更新状态用虚方法的好处时,子类根据需要去重写更新状态的方法,这样防止恶意随便更改状态
namespace Solution.Logic.Order.OrderState { public abstract class OrderState { #region 更新状态 /// <summary> /// 更新订单状态 /// </summary> /// <param name="helper">订单状态操作类</param> /// <param name="orderStateEnum">要更新的订单状态</param> protected virtual bool UpdateState(OrderStateHelper helper, OrderStateEnum orderStateEnum) { bool result = false; result = new OrderInfoBll().UpdateValue(helper.OrderId, OrderInfoTable.Status); return result; } #endregion #region 虚函数 #region 货主 /// <summary> /// 待提交 /// </summary> /// <param name="helper">订单状态操作类</param> public virtual bool PendingSubmission(OrderStateHelper helper) { return false; } /// <summary> /// 待审核 /// </summary> /// <param name="helper">订单状态操作类</param> public virtual bool PendingAudit(OrderStateHelper helper) { return false; } /// <summary> /// 审核未通过 /// </summary> /// <param name="helper">订单状态操作类</param> public virtual bool AuditNotPass(OrderStateHelper helper) { return false; } /// <summary> /// 审核通过 /// </summary> /// <param name="helper">订单状态操作类</param> public virtual bool AuditNotPass(OrderStateHelper helper) { return false; }
PendingSubmissionState(待提交状态)类
继承OrderState类,待提交可以变成待审核状态,所以重写待审核状态的方法
namespace Solution.Logic.Order.OrderState { /// <summary> /// 待提交状态类 /// </summary> public class PendingSubmissionState:OrderState { /// <summary> /// 待提交可以变成待审核状态,所以重写待审核方法 /// </summary> /// <param name="helper"></param> /// <returns></returns> public override bool PendingAudit(OrderStateHelper helper) { return UpdateState(helper, OrderStateEnum.PendingAudit); } } }
PendingAuditState(待审核状态)类
继承OrderState类,待审核状态可以变成审核通过、审核不通过,所以重写AuditNotPass(审核通过)和AuditPass(审核不通过)方法
namespace Solution.Logic.Order.OrderState { /// <summary> /// 待审核状态类 /// </summary> public class PendingAuditState : OrderState { /// <summary> /// 待审核可以变成审核未通过状态,所以重写审核未通过方法 /// </summary> /// <param name="helper">订单状态操作类</param> public override bool AuditNotPass(OrderStateHelper helper) { return UpdateState(helper, OrderStateEnum.AuditNotPass); } /// <summary> /// 待审核可以变成审核通过状态,所以重写审核通过方法 /// </summary> /// <param name="helper">订单状态操作类</param> /// <returns></returns> public override bool AuditPass(OrderStateHelper helper) { return UpdateState(helper, OrderStateEnum.AuditPass); }
AuditPassState(审核通过状态)类
继承OrderState类,由于审核通过状态已经结束,所以不重写任何方法
namespace Solution.Logic.Order.OrderState { /// <summary> /// 审核通过状态类 /// </summary> public class AuditPassState : OrderState { } }
和AuditNotPassState(审核不通过状态)类
继承OrderState类,由于审核不通过状态已经结束,所以不重写任何方法
namespace Solution.Logic.Order.OrderState { /// <summary> /// 审核不通过状态类 /// </summary> public class AuditNotPassState : OrderState { } }
OrderStateEnum(订单状态枚举)类
这里我重点说明一下 枚举状态的名字和类名去掉State后缀一样,保持这个规则,方便用反射实现订单状态的变更
namespace Solution.Logic.Order.OrderState { /// <summary> /// 订单状态枚举 /// </summary> public enum OrderStateEnum { #region 订单状态枚举 /// <summary> ///待提交 /// </summary> [Description("待提交")] PendingSubmission = 1, /// <summary> ///待审核 /// </summary> [Description("待审核")] PendingAudit = 3, /// <summary> ///审核通过 /// </summary> [Description("审核通过")] AuditPass = 5, /// <summary> ///审核未通过 /// </summary> [Description("审核未通过")] AuditNotPass = 7 #endregion 订单状态枚举 } }
调用方法如下
var orderStateHelper = new OrderStateHelper(userId, orderId, state); var isChange = orderStateHelper.ChangState(newState);
- 3楼InkFx
- 推荐+1;
- 2楼莫香邪。
- 看上去使用状态模式后的代码量好像比使用一般的switch更多。,希望能够介绍一下使用状态模式和不使用状态模式有什么区别 使用状态模式后的优点能否更详细一些?
- Re: 萧行者
- @莫香邪。,光针对代码量的话,其实都差不多。我大可把switch放到单独一个类里去做状态变更的判断和业务处理。但是如果直接用switch判断的话,耦合性就太大了,我新增一个状态的话,是不是要在switch里新增一个判断,这点违反了开闭原则,而且所有的处理放到同一个switch里 也违反了对象的单一职责。
- Re: 萧行者
- @莫香邪。,4个状态的话体现不出代码量。如果我有30多个状态的话,你一个方法里30多个判断,而且状态以后会跟着需求的变化而增加,那你这个方法是不是代码太多了。,好处是:1:我新增一个状态,我不需要去修改原有的业务逻辑的判断, 2:面向对象化,而不是面向过程。我把每一个状态当成一个对象,对象之间的联系是通过我是否重写虚函数来决定,这样降低耦合性。, 3:安全:如上面:我状态的变更不可能随便就能更改的,你可以随便更改一个状态,但是如何状态子类里没有重写对应的方法时,是不能改变的。
- 1楼玻璃鱼儿
- 不错,我们基本也是这么做的。
- Re: 萧行者
- @玻璃鱼儿,嗯嗯,现实中很多设计模式都是套在一起用的。设计模式里特别重要一点是要用反射,因为能更好的体现出开闭原则。