在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。
在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。
介绍
意图:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。
何时使用:当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为时。
如何解决:将各种具体的状态类抽象出来。
关键代码:通常命令模式的接口中只有一个方法。而状态模式的接口中有一个或者多个方法。而且,状态模式的实现类的方法,一般返回值,或者是改变实例变量的值。也就是说,状态模式一般和对象的状态有关。实现类的方法有不同的功能,覆盖接口中的方法。状态模式和命令模式一样,也可以用于消除 if...else 等条件选择语句。
应用实例: 1、打篮球的时候运动员可以有正常状态、不正常状态和超常状态。 2、活字印刷之于原始的印刷技术。
优点: 1、封装了转换规则。 2、枚举可能的状态,在枚举状态之前需要确定状态种类。 3、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。 4、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。 5、可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
缺点: 1、状态模式的使用必然会增加系统类和对象的个数。 2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。 3、状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。
使用场景: 1、行为随状态改变而改变的场景。 2、条件、分支语句的代替者。
注意事项:在行为受状态约束的时候使用状态模式,而且状态不超过 5 个。
需求:在erp里做工单流转的时候,需要根据工单的不同状态来设置该工单属于哪个角色的用户可见或者进行一些其他操作
单类写法
1 /** 2 * 在erp里做工单流转的时候,需要根据工单的不同状态来设置该工单属于哪个角色的用户可见或者进行一些其他操作 3 * @author ko 4 * 5 */ 6 public class OrderExchange { 7 8 String orderNum;// 工单号 9 int status;// 0已创建 1主任分配 2检验员检验 3报告审核人审核 4归档打印 未生成报告的工单需要计入统计(也就是0,1,2的时候) 10 boolean orderFinished = false;// 工单是否完成 流转到4的时候就是完成 11 12 public OrderExchange(String orderNum, int status, boolean orderFinished) { 13 super(); 14 this.orderNum = orderNum; 15 this.status = status; 16 this.orderFinished = orderFinished; 17 } 18 19 public void work(){ 20 if(status == 0){ 21 System.out.println(orderNum+" order status is "+status+", order's next operate person is 申请人"); 22 }else if (status == 1) { 23 System.out.println(orderNum+" order status is "+status+", order's next operate person is 主任"); 24 }else if (status == 2) { 25 System.out.println(orderNum+" order status is "+status+", order's next operate person is 检验员"); 26 }else if (status == 3) { 27 System.out.println(orderNum+" order status is "+status+", order's next operate person is 审核人"); 28 }else if (status == 4) { 29 System.out.println(orderNum+" order status is "+status+", order's next operate person is 打印员"); 30 } 31 32 if(orderFinished){ 33 System.out.println(orderNum+" order status is "+status+", order exchange finish"); 34 }else{ 35 if (status == 3) { 36 System.out.println(orderNum+" 工单状态是"+status+",不需要计入统计。。。。"); 37 }else if (status == 4) { 38 System.out.println(orderNum+" 工单状态是"+status+",工单没走完,请核查。。。。"); 39 }else{ 40 System.out.println(orderNum+" 工单状态是"+status+",需要计入统计。。。。"); 41 } 42 } 43 } 44 }
1 /** 2 * 测试类 3 * @author ko 4 * 5 */ 6 public class Test { 7 8 public static void main(String[] args) { 9 OrderExchange o1 = new OrderExchange("20151220", 0, false); 10 OrderExchange o2 = new OrderExchange("20151221", 1, false); 11 OrderExchange o3 = new OrderExchange("20151222", 2, false); 12 OrderExchange o4 = new OrderExchange("20151223", 3, false); 13 OrderExchange o5 = new OrderExchange("20151223", 4, false); 14 15 o1.work(); 16 o2.work(); 17 o3.work(); 18 o4.work(); 19 o5.work(); 20 } 21 }
20151220 order status is 0, order's next operate person is 申请人 20151220 工单状态是0,需要计入统计。。。。 20151221 order status is 1, order's next operate person is 主任 20151221 工单状态是1,需要计入统计。。。。 20151222 order status is 2, order's next operate person is 检验员 20151222 工单状态是2,需要计入统计。。。。 20151223 order status is 3, order's next operate person is 审核人 20151223 工单状态是3,不需要计入统计。。。。 20151223 order status is 4, order's next operate person is 打印员 20151223 工单状态是4,工单没走完,请核查。。。。
观察上面的代码,虽然可以实现我们的需求,但是work方法过长了,并且所有的状态判断都写在这里面,说明OrderExchange类责任重大,无论是什么状态,都要通过它来改变,这很糟糕,面向对象设计其实就是希望做到代码的责任分解,这个类违背了‘单一职责原则’。
由于work方法过长,判断分支过多,会导致需求有增减和改动的时候特别不方便,还容易出错,比方说需要把不管什么状态的工单都纳入统计,其实只要改状态为3、4时候的判断,但是我们动的却是整个work方法,这样违背了‘开放封闭原则’。
状态模式写法
1 /** 2 * 抽象工单状态类 3 * @author ko 4 * 5 */ 6 public abstract class OrderState { 7 8 /** 9 * 封装与context一个特定状态相关的行为 10 * @param context 11 */ 12 public abstract void handle(Context context); 13 }
1 /** 2 * 具体状态类 新建工单 3 * @author ko 4 * 5 */ 6 public class XinjianState extends OrderState { 7 8 /** 9 * 新建工单(申报人可见)的下一个状态是待分配 10 */ 11 @Override 12 public void handle(Context context) { 13 context.setState(new DaifenpeiState()); 14 System.out.println("新建工单(申报人可见)的下一个状态是待分配"); 15 } 16 17 }
1 /** 2 * 具体状态类 待分配 3 * @author ko 4 * 5 */ 6 public class DaifenpeiState extends OrderState { 7 8 /** 9 * 待分配(科室主任可见)的任务下一个状态是待检验 10 */ 11 @Override 12 public void handle(Context context) { 13 context.setState(new DaijianyanState()); 14 System.out.println("待分配(科室主任可见)的任务下一个状态是待检验"); 15 } 16 17 }
1 /** 2 * 具体状态类 待检验 3 * @author ko 4 * 5 */ 6 public class DaijianyanState extends OrderState { 7 8 /** 9 * 待检验(检验员可见)的工单下一状态是待审核 10 */ 11 @Override 12 public void handle(Context context) { 13 // TODO Auto-generated method stub 14 context.setState(new DaishenheState()); 15 System.out.println("待检验(检验员可见)的工单下一状态是待审核"); 16 } 17 18 }
1 /** 2 * 具体状态类 待审核 3 * @author ko 4 * 5 */ 6 public class DaishenheState extends OrderState { 7 8 /** 9 * 待审核(总工程师可见)工单的下一状态是待归档 10 */ 11 @Override 12 public void handle(Context context) { 13 // TODO Auto-generated method stub 14 context.setState(new DaiguidangState()); 15 System.out.println("待审核(总工程师可见)工单的下一状态是待归档"); 16 } 17 18 }
1 /** 2 * 具体状态类 待归档 3 * @author ko 4 * 5 */ 6 public class DaiguidangState extends OrderState { 7 8 /** 9 * 待归档(打印员)工单没有下一状态了 10 */ 11 @Override 12 public void handle(Context context) { 13 context.setState(null); 14 System.out.println("待归档(打印员)工单没有下一状态了"); 15 } 16 17 }
1 /** 2 * 维护具体状态类的实例,这个实例定义当前的状态 3 * @author ko 4 * 5 */ 6 public class Context { 7 8 private OrderState state;// 工单状态 9 10 /** 11 * 构造函数,传入某一工单状态 12 * @param state 13 */ 14 public Context(OrderState state) { 15 super(); 16 this.state = state; 17 } 18 19 public OrderState getState() { 20 return state; 21 } 22 23 public void setState(OrderState state) { 24 this.state = state; 25 } 26 27 /** 28 * 对请求做处理,并设置下一状态 29 */ 30 public void request(){ 31 state.handle(this); 32 } 33 34 }
1 /** 2 * 测试类 3 * @author ko 4 * 5 */ 6 public class Test { 7 8 public static void main(String[] args) { 9 Context context = new Context(new XinjianState()); 10 context.request(); 11 context.request(); 12 context.request(); 13 context.request(); 14 context.request(); 15 } 16 }
新建工单(申报人可见)的下一个状态是待分配 待分配(科室主任可见)的任务下一个状态是待检验 待检验(检验员可见)的工单下一状态是待审核 待审核(总工程师可见)工单的下一状态是待归档 待归档(打印员)工单没有下一状态了
观察代码可以发现,状态模式是将特定的状态相关的行为放到一个对象中,由于所有与状态相关的代码都存放在某个具体state类中,所以对各个状态的增删改其实就是对子类state的增删改,这样比单类的写法清晰和容易维护多了。
状态模式的目的就是为了消除庞大的条件分支语句,大的分支会使业务逻辑难以修改和扩展。状态模式用过把各种状态转移逻辑分不到state的子类之间,来减少相互之间的依赖。