行为型模式
目录
1、状态模式
在状态模式(State Pattern)中,类的行为是基于它的状态改变的。在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。
- 意图:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
- 主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。
- 何时使用:代码中包含大量与对象状态有关的条件语句。
- 如何解决:将各种具体的状态类抽象出来。
- 关键代码:通常命令模式的接口中只有一个方法。而状态模式的接口中有一个或者多个方法。而且,状态模式的实现类的方法,一般返回值,或者是改变实例变量的值。也就是说,状态模式一般和对象的状态有关。实现类的方法有不同的功能,覆盖接口中的方法。状态模式和命令模式一样,也可以用于消除 if...else 等条件选择语句。
1.1 状态模式UML图
1.2 日常生活中看状态模式与应用实例
- 大话设计模式中的上班时上午状态好、中午想睡觉、下午逐渐恢复、加班苦煎熬。这就是一种状态的变化,不同的时间,会有不同的状态。
- 以水为例,水之三态,固、液、气,三种状态表现出不同的特性和行为,它们之间的转换也伴随着热力学的现象。
- 电梯,有运行状态、开门状态、闭门状态、停止状态等
- 一日从早到晚自身的状态,比如工作状态、学习状态等等
- 运动员可以有正常状态、非正常状态和超长状态
1.3 Java代码实现
根据上面UML图
首先我们先定义一个State抽象状态类,里面定义了一个接口以封装 与Context的一个特定状态相关的行为;
/**
* 抽象状态类
* @author gh
*
*/
public abstract class State {
public abstract void Handle(Context context);
}
接着再去声明一个ConcreteState具体状态类,每一个子类实现一个与Context的一个状态的相关的行为。
public class ConcreteStateA extends State{
@Override
public void Handle(Context context) {
context.setState(new ConcreteStateB()); //设置A的下一个状态是B
}
}
class ConcreteStateB extends State{
@Override
public void Handle(Context context) {
context.setState(new ConcreteStateA()); //设置B的下一个状态是A
}
}
Context类,维护一个ConcreteState子类的实例,这个实例定义当前的状态
/**
* 定义当前的状态
* @author gh
*
*/
public class Context {
State state;
public Context(State state) { //定义Context的初始状态
super();
this.state = state;
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
System.out.println("当前状态为"+state);
}
public void request(){
state.Handle(this); //对请求做处理并且指向下一个状态
}
}
提到状态模式,让我想到了工作流,工作流就是控制一个一个的节点状态来实现节点的跳转,最后来控制流程。
如果上面发起了一个请假流程,这个时候第一个节点就是部门领导审核,部门领导审核通过会继续往下走,如果不通过那么有两种状态,一种是直接驳回请求,领导说,项目最近很急,任何人都不能请假,还有一种是你写的请假申请单不对,要退回整改重新写。审核通过后就进入下一个节点,人力资源部门审核,当然人力资源也可以驳回请求,或者要你重新整改,人力资源审核通过之后就可以休假了,这个时候还可以选择是否发送Email。
/**
* 节点接口
* @author gh
*
*/
public abstract class Node {
private static String name; //当前节点名称
//节点跳转
public abstract void nodeHandle(FlowContext context);
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
相当于State类,这里维护一个节点名称。
/**
* 领导节点
*
* @author gh
*
*/
public class LeadNode extends Node {
@Override
public void nodeHandle(FlowContext context) {
//根据当前流程的状态,来控制流程的走向
//先判断流程是否结束
if(!context.isFlag()){
System.out.println(context.getMessage()); //先读取申请的内容
if(context!=null&&3==context.getStatus()){ //只有出于已经申请的状态才又部门领导审核
//设置当前节点的名称
setName("张经理");
//加上审核意见
context.setMessage(context.getMessage()+getName()+"审核通过;");
//审核通过
context.setStatus(0); //审核通过并指向下一个节点
context.setNode(new HrNode());
context.getNode().nodeHandle(context);
}
}else{
System.err.println("流程已经结束");
}
}
}
这里创建了一个领导节点,用来维护领导审核的流程,审核通过会交给HR审核;
public class HrNode extends Node {
@Override
public void nodeHandle(FlowContext context) {
//先判断流程是否结束
if(!context.isFlag()){
// 根据当前流程的状态,来控制流程的走向
if (context != null &&
0 == context.getStatus()) { //只有上一级审核通过后才能轮到HR审核
// 设置当前节点的名称
setName("HR李");
//读取上一级的审核内容并加上自己的意见
System.out.println(context.getMessage()+getName()+"审核通过");
// 审核通过
context.setStatus(0); //HR审核通过并指向下一个节点 ,如果没有下一个节点就把状态设置为终结
context.setFlag(true);
}
}else{
System.out.println("流程已经结束");
}
}
}
这里HR审核通过并把节点设置为完结状态;
/**
* 流程控制
*
* @author gh
*
*/
public class FlowContext {
private boolean flag; // 代表流程是否结束
/**
* 流程状态 0:通过 1:驳回 2.退回整改 3.已申请
*
*/
private int status;
private String message; // 消息
private Node node; // 节点信息
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Node getNode() {
return node;
}
public void setNode(Node node) {
this.node = node;
}
public static boolean start(FlowContext context) {
Node node = new LeadNode();
context.setNode(node); // 设置初始节点
context.setStatus(3); // 设置状态为申请中
context.getNode().nodeHandle(context); // 发起请求
// 最后要知道是否申请成功
//判断当前是最后一个节点并且审核通过,而且流程结束
if("HR李".equals(node.getName())&&0==context.getStatus()&&context.isFlag()){
System.out.println("审核通过,流程结束");
return true;
}else{
System.out.println("审核未通过,流程已经结束");
return false;
}
}
public FlowContext() {
super();
}
}
这里维护一个流程控制类,它会在HR和LEAD节点之后传递,并分别由他们去维护各自的节点。
最后写一个测试类测试一下:
public static void main(String[] args) {
FlowContext context=new FlowContext();
context.setMessage("本人王小二,因为十一家里有事情,所以要多请三天假,希望公司能够审核通过");
context.start(context);
}
打印结果如下
本人王小二,因为十一家里有事情,所以要多请三天假,希望公司能够审核通过
本人王小二,因为十一家里有事情,所以要多请三天假,希望公司能够审核通过张经理审核通过;HR李审核通过
审核通过,流程结束;
上面这个例子只是很简单的模仿了一下工作流控制状态的跳转。状态模式最主要的好处就是把状态的判断与控制放到了其服务端的内部,使得客户端不需要去写很多代码判断,来控制自己的节点跳转,而且这样实现的话,我们可以把每个节点都分开来处理,当流程流转到某个节点的时候,可以去写自己的节点流转方法。当然状态模式的缺点也很多,比如类的耦合度比较高,基本上三个类要同时去写,而且会创建很多的节点类。
2、JSF源码中状态模式体现
JSF 是一个比较经典的前端框架,没用过的小伙伴也没关系,这里只是分析一下其设计思想。
在 JSF 中,所有页面的处理分为 7 个阶段,被定义在 PhaseId 类中,分别用不同的常量来表示周期阶段,源码如下。
private class PhaseId implements Comparable {
...
private static final PhaseId[] values = {
ANY_PHASE, // 任意一个生命周期阶段
RESTORE_VIEW, // 恢复视图阶段
APPLY_REQUEST_VALUES, // 应用请求值阶段
PROCESS_VALIDATIONS, // 处理输入校验阶段
UPDATE_MODEL_VALUES, // 更新模型的值阶段
INVOKE_APPLICATION, // 调用应用阶段
RENDER_RESPONSE // 显示响应阶段
};
...
}
这些状态的切换都在 Lifecycle 类的 execute() 方法中进行,其中会传入一个参数 FacesContext 对象,最终所有状态都被 FacesContext 保存。在此不深入分析。
package javax.faces.lifecycle;
import javax.faces.FacesException;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseListener;
public abstract class Lifecycle {
public Lifecycle() {
}
public abstract void addPhaseListener(PhaseListener var1);
public abstract void execute(FacesContext var1) throws FacesException;
public abstract PhaseListener[] getPhaseListeners();
public abstract void removePhaseListener(PhaseListener var1);
public abstract void render(FacesContext var1) throws FacesException;
}
3、状态模式优缺点
3.1 优点
- 封装了转换规则。
- 枚举可能的状态,在枚举状态之前需要确定状态种类。
- 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
- 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。结构清晰,避免了过多的switch…case或if…else语句的使用
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
3.2 缺点
- 状态模式的使用必然会增加系统类和对象的个数。子类会太多,也即类膨胀
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
- 状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。
3.3 使用场景
- 行为随状态改变而改变的场景
- 条件、分支判断语句的替代者
3.4 注意事项
- 在行为受状态约束的情况下可以使用状态模式,使用时对象的状态最好不要超过5个
4、总结
通过上面的例子,我们已经对状态模式有所了解,下面我们做一个总结,来回顾我们的状态模式:
1.状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
理解:这个模式将状态封装成独立的类,并将动作委托到代表当前状态的对象,这就是说行为会随着内部状态而改变。
“看起来好像修改了它的类”是什么意思呢?从客户的视角来看:如果说你使用的对象能够完全改变它的行为,那么你会觉得,这个对象实际上是从别的类实例化而来的。然而,实际上,你知道我们是在使用组合通过简单引用不同的状态对象来造成类改变的假象
4.1 状态模式要点
- (1)客户不会和状态进行交互,全盘了解状态是 context的工作
- (2)在状态模式中,每个状态通过持有Context的引用,来实现状态转移
- (3)使用状态模式总是会增加设计中类的数目,这是为了要获得程序可扩展性,弹性的代价,如果你的代码不是一次性的,后期可能会不断加入不同的状态,那么状态模式的设计是绝对值得的。【同时也是一个缺点】
- (4)状态类可以被多个context实例共享
4.2 状态模式和策略模式对比
首先让我们来看看它们之间更多的相似之处:
- 添加新的状态或策略都很容易,而且不需要修改使用它们的Context对象。
- 它们都让你的代码符合OCP原则(软件对扩展应该是开发的,对修改应该是关闭的)。在状态模式和策略模式中,Context对象对修改是关闭的,添加新的状态或策略,都不需要修改Context。
- 正如状态模式中的Context会有初始状态一样,策略模式同样有默认策略。
- 状态模式以不同的状态封装不同的行为,而策略模式以不同的策略封装不同的行为。
- 它们都依赖子类去实现相关行为
两个模式的差别在于它们的”意图“不同:
状态模式帮助对象管理状态,我们将一群行为封装早状态对象中,context的行为随时可委托到那些状态中的一个.随着时间的流逝,当前状态在状态对象集合中游走改变,以反映context内部状态,因此,context的行为也会跟着改变。当要添加新的状态时,不需要修改原来代码添加新的状态类即可。 而策略模式允许Client选择不同的行为。通过封装一组相关算法,为Client提供运行时的灵活性。Client可以在运行时,选择任一算法,而不改变使用算法的Context。一些流行的策略模式的例子是写那些使用算法的代码,例如加密算法、压缩算法、排序算法。客户通常主动指定context所要组合的策略对象是哪一个.
一句话:最根本的差异在于策略模式是在求解同一个问题的多种解法,这些不同解法之间毫无关联;状态模式则不同,状态模式要求各个状态之间有所关联,以便实现状态转移。
参考文章:
http://c.biancheng.net/view/8493.html
https://www.jianshu.com/p/5bf844141687