标签:
俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及的总结知识点如下:
前面有总结——策略模式,之前早就觉得策略和状态设计模式有一些相似……
我知道策略模式是对象的行为模式,其实就是对一系列级别平等的算法的封装,它不关心算法实现,让客户端去动态的依靠 “环境” 类去选择需要的算法,因为他们能互相替换,可以说策略模式使能一系列算法可以平滑的切换。那么状态(State)模式,也是对象的行为设计模式的一种。
官方教科书是这样定义的:状态模式允许通过改变对象的内部状态而改变对象的行为,这个对象表现得就好像修改了它的类一样。呵呵,记得早之前学设计模式,学到状态模式的概念,这么炸一看,这是解释的鸡毛啊…… 先直接看个小例子,顺着它的定义来推演:
1 /** 2 * Person 3 * 4 * @author Wang Yishuai. 5 * @date 2016/2/5 0005. 6 * @Copyright(c) 2016 Wang Yishuai,USTC,SSE. 7 */ 8 public class Person { 9 /** 10 * 这个人有一个闹表,靠它的时间变化(状态修改)来决定何时做什么(改变行为) 11 */ 12 private int hour; 13 14 public int getHour() { 15 return hour; 16 } 17 18 public void setHour(int hour) { 19 this.hour = hour; 20 } 21 22 /** 23 * 人的一个行为 24 * 25 * 状态模式允许通过改变一个对象的内部状态,来改变对象的行为,就像修改了对象的类一样! 26 */ 27 public void doSth() { 28 // 那么我就模拟修改类的对象的内部状态 29 if (this.hour == 7) { 30 System.out.println("起床啦!"); 31 } else if (this.hour == 11) { 32 System.out.println("吃中午饭了!"); 33 } else if (this.hour == 19) { 34 System.out.println("吃晚饭了!"); 35 } else if (this.hour == 22) { 36 System.out.println("睡觉咯!"); 37 } else { 38 System.out.println("学习呢!"); 39 } 40 } 41 } 42 43 public class MainState { 44 public static void main(String[] args) { 45 Person person = new Person(); 46 47 person.setHour(7); 48 person.doSth();// 起床啦! 49 50 person.setHour(11); 51 person.doSth();// 吃中午饭了! 52 53 person.setHour(19); 54 person.doSth();// 吃晚饭了! 55 56 person.setHour(22); 57 person.doSth();// 睡觉咯! 58 59 person.setHour(10); 60 person.doSth();// 学习呢! 61 } 62 }
这个例子,确实就是状态模式描述的场景,有一个Person类,代表(个)人,它有一个时间对象——闹表,通过闹表的时间的变化(修改对象的内部状态)来改变对象的行为(人的一些睡觉,学习的行为),这个对象表现的就好比修改了它的类一样。但是,这个例子并没有使用所谓的状态设计模式来实现,Person类设计的很low!大量的if-else不易维护……那么这个场景下,应该使用本文提到的状态模式。尝试实现:
1 public abstract class State { 2 /** 3 * 抽象状态(接口)角色,封装了和环境类(Person类)的对象的状态(闹表时间的变化)相关的行为 4 */ 5 public abstract void doSth(); 6 } 7 8 public class GetUp extends State { 9 /** 10 * 各个具体的状态角色,实现状态类, 11 */ 12 @Override 13 public void doSth() { 14 System.out.println("起床啦!"); 15 } 16 } 17 18 public class HaveDinner extends State { 19 @Override 20 public void doSth() { 21 System.out.println("吃晚饭了!"); 22 } 23 } 24 25 public class HaveLunch extends State { 26 @Override 27 public void doSth() { 28 System.out.println("吃中午饭了!"); 29 } 30 } 31 32 public class Sleep extends State { 33 @Override 34 public void doSth() { 35 System.out.println("睡觉咯!"); 36 } 37 } 38 39 public class Study extends State { 40 @Override 41 public void doSth() { 42 System.out.println("学习呢!"); 43 } 44 } 45 46 public class Person { 47 /** 48 * 这个人有一个闹表,靠它的时间变化(状态修改)来决定何时做什么(改变行为) 49 */ 50 private int hour; 51 52 private State state; 53 54 public int getHour() { 55 return hour; 56 } 57 58 public void setHour(int hour) { 59 this.hour = hour; 60 } 61 62 /** 63 * 人(环境类)的个行为 64 * 65 * 状态模式允许通过改变一个对象的内部状态,来改变对象的行为,就像修改了对象的类一样! 66 */ 67 public void doSth() { 68 if (this.hour == 7) { 69 state = new GetUp(); 70 state.doSth(); 71 } else if (this.hour == 11) { 72 state = new HaveLunch(); 73 state.doSth(); 74 } else if (this.hour == 19) { 75 state = new HaveDinner(); 76 state.doSth(); 77 } else if (this.hour == 22) { 78 state = new Sleep(); 79 state.doSth(); 80 } else { 81 state = new Study(); 82 state.doSth(); 83 } 84 } 85 } 86 87 public class MainStateA { 88 public static void main(String[] args) { 89 Person person = new Person(); 90 91 person.setHour(7); 92 person.doSth();// 起床啦! 93 94 person.setHour(11); 95 person.doSth();// 吃中午饭了! 96 97 person.setHour(19); 98 person.doSth();// 吃晚饭了! 99 100 person.setHour(22); 101 person.doSth();// 睡觉咯! 102 103 person.setHour(10); 104 person.doSth();// 学习呢! 105 } 106 }
确实有了变化,把之前的Person类对象的内部状态的改变对应的Person的行为的变化做了封装,变成了具体实现抽象的类来表示,但是并没有什么实质上的改变!
Person类依然有大量不易维护的if-else语句,而状态模式的使用目的就是控制一个对象状态转换的条件表达式过于复杂时的情况——把状态的判断逻辑转译到表现不同状态的一系列类当中,可以把复杂的判断逻辑简化。上一版本没有把对应状态的判断逻辑同时转移,还是留在了环境类(Person类)里……继续优化:
public abstract class State { /** * 抽象状态(接口)角色,封装了和环境类(Person类)的对象的状态(闹表时间的变化)相关的行为 */ public abstract void doSth(PersonB personB); } public class GetUp extends State { /** * 各个具体的状态角色,实现状态类, */ @Override public void doSth(PersonB personB) { if (personB.getHour() == 7) { System.out.println("起床啦!"); } else { // 转移状态 personB.setState(new HaveLunch()); // 必须要调用行为 personB.doSth(); } } } public class HaveDinner extends State { @Override public void doSth(PersonB personB) { if (personB.getHour() == 19) { System.out.println("吃晚饭了!"); } else { personB.setState(new Sleep()); personB.doSth(); } } } public class HaveLunch extends State { @Override public void doSth(PersonB personB) { if (personB.getHour() == 11) { System.out.println("吃中午饭了!"); } else { personB.setState(new HaveDinner()); personB.doSth(); } } } public class Sleep extends State { @Override public void doSth(PersonB personB) { if (personB.getHour() == 22) { System.out.println("睡觉咯!"); } else { personB.setState(new Study()); personB.doSth(); } } } public class Study extends State { @Override public void doSth(PersonB personB) { // 如此,再也不需要向下传递状态了! System.out.println(personB.getHour() + "点,正学习呢!"); } }
把之前放到环境类里的对当前对象状态的逻辑判断(条件表达式……),随着不同的状态放到了对应的状态类里!!!且同时让状态动态的迁移——这里又有责任链模式的影子了。而且继承的抽象状态类的行为方法里加上了环境类的对象作为参数。以起床状态为例:
public void doSth(PersonB personB) { if (personB.getHour() == 7) { System.out.println("起床啦!"); } else { // 转移状态 personB.setState(new HaveLunch()); // 必须要调用行为 personB.doSth(); } }
当getup状态类的if判断不满足时,必须记得转移状态到下一个,set一个新状态去覆盖旧状态……同时记得调用下一个状态的行为(执行方法)。
PS:这里非常像责任链(职责链)模式的思想。最后一个状态学习类,没有转移的其他状态了,那么就不需要转移了呗,直接设置为终结状态(在责任链模式里是依靠判断get到的链接对象是否为null来判断职责链条的终点的)。如下:
public class Study extends State { @Override public void doSth(PersonB personB) { // 如此,最后一个状态(或者说代表其他的状态)再也不需要向下传递状态了! System.out.println(personB.getHour() + "点,正学习呢!"); } }
再看环境类,和客户端(客户端代码不需要变化)
public class PersonB { /** * 这个人有一个闹表,靠它的时间变化(状态修改)来决定何时做什么(改变行为) */ private int hour; private State state; public State getState() { return state; } public void setState(State state) { this.state = state; } public int getHour() { return hour; } public void setHour(int hour) { this.hour = hour; } public PersonB() { // 在构造器里初始化状态,从早晨起床开始 this.state = new GetUp(); } /** * 人(环境类)的个行为 * * 状态模式允许通过改变一个对象的内部状态,来改变对象的行为,就像修改了对象的类一样! */ public void doSth() { // 传入的是PersonB的对象 state.doSth(this); } } public class MainStateB { public static void main(String[] args) { PersonB personB = new PersonB(); personB.setHour(7); personB.doSth(); personB.setHour(11); personB.doSth(); personB.setHour(19); personB.doSth(); personB.setHour(22); personB.doSth(); personB.setHour(10); personB.doSth(); } }
打印:
起床啦!
吃中午饭了!
吃晚饭了!
睡觉咯!
10点,正学习呢!
貌似ok了,这时人睡觉到第二天了,早晨又该起床了……
public class MainStateB { public static void main(String[] args) { PersonB personB = new PersonB(); personB.setHour(7); personB.doSth(); personB.setHour(11); personB.doSth(); personB.setHour(19); personB.doSth(); personB.setHour(22); personB.doSth(); personB.setHour(10); personB.doSth(); personB.setHour(7); personB.doSth();// 有问题 } }
客户端顺序增了一个7点的状态,发现打印如下:
起床啦!
吃中午饭了!
吃晚饭了!
睡觉咯!
10点,正学习呢!
7点,正学习呢!
不对啊!7点应该是起床啦!说明我之前的状态模式的实现代码还是不完美!问题出在环境类(Person类)的初始化上,客户端new了一个人,则person的构造器自动初始化状态为getup起床,当把对象的内部状态修改,那么会去寻找对应的状态类,找不到就迁移到下一个状态,它的状态迁移是单向不可逆的……如图:
继续优化如下,只需要修改环境类Person,把环境的对象的内部状态初始化工作放到行为里;
public class PersonB { /** * 这个人有一个闹表,靠它的时间变化(状态修改)来决定何时做什么(改变行为) */ private int hour; private State state; public State getState() { return state; } public void setState(State state) { this.state = state; } public int getHour() { return hour; } public void setHour(int hour) { this.hour = hour; } public PersonB() { // 在构造器里初始化状态,从早晨起床开始 this.state = new GetUp(); } /** * 人(环境类)的个行为 * * 状态模式允许通过改变一个对象的内部状态,来改变对象的行为,就像修改了对象的类一样! */ public void doSth() { // 每次都从头开始搜索状态类 this.state = new GetUp(); // 传入的是PersonB的对象 state.doSth(this); } }
运行之后,又发现了一个问题,这里我把这个问题记录了,真实too young!naive啊!这样搞发生了循环调用问题。再次修改如下,核心思想其实是每次对象内部状态改变之后,都把状态迁移复位一下。记住是之后复位。
public class PersonB { /** * 这个人有一个闹表,靠它的时间变化(状态修改)来决定何时做什么(改变行为) */ private int hour; private State state; public State getState() { return state; } public void setState(State state) { this.state = state; } public int getHour() { return hour; } public void setHour(int hour) { this.hour = hour; } public PersonB() { // 在构造器里初始化状态,从早晨起床开始 this.state = new GetUp(); } /** * 人(环境类)的个行为 * * 状态模式允许通过改变一个对象的内部状态,来改变对象的行为,就像修改了对象的类一样! */ public void doSth() { // 传入的是PersonB的对象 state.doSth(this); // 每次都从头开始搜索状态类 this.state = new GetUp(); } }
这样就ok了,小结:状态模式隐含着责任链模式的部分思想,而UML类图的设计上和策略模式非常相似!下面继续分析。
状态模式都有哪些角色?画出类图?
是不是和策略模式的类图很像很像!同样的一个抽象类(接口),包含一个行为,和N个具体实现的类,外加一个环境类(聚合了接口引用)……
状态模式和策略模式的比较
小结:状态模式的使用场景是什么?
状态模式主要解决的是(目的or意图):控制一个对象内部的状态转换的条件表达式过于复杂时的情况,且客户端调用之前不需要了解具体状态。它把状态的判断逻辑转到表现不同状态的一系列类当中,可以把复杂的判断逻辑简化。维持开闭原则,方便维护
,还有重要一点下面会总结,状态模式是让各个状态对象自己知道其下一个处理的对象是谁!即在状态子类编译时在代码上就设定好了!
状态模式的优缺点都是什么?
优点,前面说了很多了……
缺点:
使用状态模式时,每个状态对应一个具体的状态类,使结构分散,类的数量变得很多!使得程序结构变得稍显复杂,阅读代码时相对之前比较困难,不过对于优秀的研发人员来说,应该是微不足道的。因为想要获取弹性!就必须付出代价!除非我们的程序是一次性的!用完就丢掉……如果不是,那么假设有一个系统,某个功能需要很多状态,如果不使用状态模式优化,那么在环境类(客户端类)里会有大量的整块整块的条件判断语句!
这才尼玛是真正的变得不好理解!lz我是实习生!我在本公司(匿名了)就见过有亲人写这样的代码,一个方法或者一个类,动不动几千行代码……重要的是里面一大块一大块的if-else……还倍感优越!看,我写的快不快……
状态模式恰恰是看着类多了,其实是让状态变的清晰,让客户端和环境类都彼此干净!更加方便理解和维护!
实际编程中,面对大量的if-else,switch-case逻辑判断,如何优化?
有时业务不是很复杂,参数校验不是很多的时候,当然可以使用if或者if-else逻辑块或者switch-case块来进行编码,但是一旦扩展了程序,增加了业务,或者开始就有很多很多的逻辑判断分支,这并不是一件好事,它首先不满足OCP——开闭原则,一旦需要修改判断方法或者类,那么牵一发动全身,常常整个逻辑块都需要大改,责任没有分解,对象内部状态的改变和对应逻辑都杂糅在了一起,也不符合单一职责原则,恰恰此时,我希望分解整个判断过程,分离职责,把状态的判断逻辑转移到表示不同状态的一系列类当中,把复杂的判断逻辑简化,这就是刚刚说的状态模式。状态模式把当前类对象的内部的各种状态转移逻辑分布到State抽象类的子类中,这样减少了各个逻辑间的依赖,客户端也不需要实现了解各个状态。
不过,综上总结,我发现,状态模式是让各个状态对象自己知道其下一个处理的对象是谁!即在编译时在代码上就设定好了!比如之前例子的状态子类:
public class GetUp extends State { /** * 各个具体的状态角色,实现状态类, */ @Override public void doSth(PersonB personB) { if (personB.getHour() == 7) { System.out.println("起床啦!"); } else { // 转移状态,明确知道 要转移到哪个 已有 的状态! personB.setState(new HaveLunch()); // 必须要调用对应状态的行为 personB.doSth(); } } }
如果有一种复杂逻辑判断,比如公司考勤系统处理员工请假的流程,不同级别,类型,部门等的员工的请假流程是不一样的!我们无法知道员工该状态的下一个状态是什么!老王是临时工,请假只需要直接领导批准,老李是正式工,请假需要先让直接领导审批,再交给主管批准,老张是安全部门的员工,请假需要的流程更复杂……或者哪天系统变化升级,请假制度修改了……换句话说就是请假系统里请假相关的各个对象并不指定(也不知道)其下一个处理的对象到底是谁,只有在客户端才设定。这怎么办?这就需要责任链设计模式解决,两者类图不一样,具体解耦责任,转移对象的流程略微的不一样,但是总的目标一致:
大体上看,责任链模式要比状态模式灵活,虽然职责链模式虽然灵活,但是遵循够用原则!比如前面的状态模式的例子:Person类的闹表记录一天的状态及其对应的行为,各个状态(判断逻辑)明确知道其下一个状态(处理对象)是谁!在内部编码时就确定了,状态模式就ok了,用责任链就显得很呵呵,适合就好!
还有简单情景下,可以使用三元运算符 condition ? : 代替简单的if-else语句,或者数组这种随机存储乃至查询性能很好的数据结构替换switch-case。但是我想的是设计模式的阴暗面!不要为了用设计模式而用设计模式!对于switch-case语句块,也不要过度优化,数量不是很大时,switch的性能也不差,没必要优化什么,想起来《Java编程思想》作者埃克尔说的,等到迫不得已必须要这么做的时候,再想优化,不要陷入优化和设计模式的陷阱。
JDK里都有哪些类有状态模式的应用?
Java集合框架专题:
什么是有限状态机?在Java中有什么应用?
先看教科书的具体定义:(Finite-state machine, FSM),又称有限状态自动机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。FSM是一种算法思想,简单而言,有限状态机由一组状态、一个初始状态、输入和根据输入及现有状态转换为下一个状态的转换函数组成。而本文总结的State模式(状态模式)其实本质就是一种面向对象的状态机思想,可以适应非常复杂的状态管理。
它反映从系统开始到现在时刻输入的变化,以及各个状态之间转移的指示变更,而且必须使用能满足状态转移的条件来描述,状态机里的动作是在给定时刻要进行的活动描述,状态机里的状态存储的是有关于过去的信息,它有多种类型的动作:
说了那么多,它到底能干嘛的呢,其实不论编程还是生活里,状态机无时不在!我知道,编程是对现实的抽象,状态机也是,当业务逻辑里有大量逻辑判断需要各种来回的转换状态时,有限状态机就有用了,本质上其是用查表法把处理逻辑独立到表中:
可以用通用的代码去处理任意复杂的状态转换,扩展开来,任何复杂状态逻辑的处理都可以比如:
标签:
原文地址:http://www.cnblogs.com/kubixuesheng/p/5180509.html