设计模式之策略模式和状态模式(strategy pattern & state pattern)
本文来讲解一下两个结构比较相似的行为设计模式:策略模式和状态模式。两者单独的理解和学习都是比较直观简单的,但是实际使用的时候却并不好实践,算是易学难用的设计模式吧。这也是把两者放在一起介绍的原因,经过对比和实例介绍,相信应该会一些比较深刻的感知。最后在结合个人的体会简单聊一下对这两个模式的一些看法。
1. 模式概念
1.1 策略模式
运行时更改类的行为或算法,从而达到修改其功能的目的;
使用场景: 一个系统需要动态地在几种算法中选择一种,而这些算法之间仅仅是他们的行为不同。 此外决策过程中过多的出现if else,也可以考虑使用该模式。
实现:将这些算法封装成可单独运行的类,由使用者根据需要进行替换。
优点: 较为灵活,扩展性好,避免大量的if else结构。
缺点: 对外暴露了类所有的行为和算法,行为过多导致策略类膨胀。
1.2 状态模式
运行时类的行为由其状态决定;
使用场景: 对象依赖装填,行为随状态改变而改变的情景,或者存在大量的if else和分支结构等;
实现:将对象的状态封装成单个的类,每个状态处理该状态下的事务,并控制该状态到其他状态的转移;
优点: 容易新加状态,封装了状态转移规则,每个状态可以被复用和共享,避免大量的if else结构。
缺点: 该模式结构和实现相对复杂,状态过多导致增加类和对象个数。同时由于由每个状态控制向其他状态的转移,新加状态必须要修改现有的部分状态才能加入状态机中生效。
1.3 相同点
两者通过将行为和状态拆分成一系列小的组件,由条件和状态进行功能更替,这样符合开闭原则,便于扩展。此外均可作为if else或者分支的替换方案;支持的最大行为和状态均有限;
1.4 不同点
- 策略模式中,类的功能是根据当前条件主动更改;
- 状态模式中,类的功能是被动由当前状态更改;
- 策略模式中每个行为或算法之间没有关联;
- 状态模式中的状态之间有关联,并且状态本身控制着状态转移;
2. 原理
两种模式的结构非常相似,下面分别看一下两种设计模式的UML类图:
2.1 策略模式UML
描述:
Context:
使用了某种策略的类,其行为由其包含的具体的策略决定,该类能主动修改使用的策略从而改变其行为;
Strategy:
抽象策略类,用于定义所有支持的算法公共接口;
ConcreteStrategy:
能够被Context使用的具体的策略;
2.2 状态模式UML
描述:
Context:
带有某个状态标记的类,其行为由其当前的状态决定,类状态的转移由状态来控制;
State:
抽象状态类,用于定义Context所有状态的公共接口;
ConcreteState:
Context类的某种具体状态,包含了该状态下处理的事务并控制向他状态转移;
3. 实例——策略模式
举个压缩软件使用不同压缩策略的例子。
抽象策略接口:Compression
public interface Compression { public void doCompression(); }
快速压缩算法:Rapid
public class Rapid implements Compression { @Override public void doCompression() { System.out.println("Use rapid compression strategy!"); } }
高效压缩算法:Efficient
public class Efficient implements Compression { @Override public void doCompression() { System.out.println("Use efficient compression strategy!"); } }
加密压缩算法:Encrypt
public class Encrypt implements Compression { @Override public void doCompression() { // TODO Auto-generated method stub System.out.println("Use encrypt compression strategy!"); } }
集成上面压缩算法的软件:WinRAR
public class WinRAR { private Compression compression = null; public WinRAR(Compression compression) { this.compression = compression; } public void setStrategy(Compression compression) { this.compression = compression; } public void compression() { if (compression != null) { compression.doCompression(); } } }
演示:
public class Demo { public static void main(String[] args) { WinRAR winrar = new WinRAR(new Rapid()); winrar.compression(); winrar.setStrategy(new Efficient()); winrar.compression(); winrar.setStrategy(new Encrypt()); winrar.compression(); } }
结果:
Use rapid compression strategy! Use efficient compression strategy! Use encrypt compression strategy!
这个例子看着很直观,后面会给出一点分析和个人的理解。
4. 实例——状态模式
我们通过自动洗衣机工作过程来描述一下状态模式使用。
简单起见,这里我们仅仅考虑【开始】-> 【工作】-> 【结束】,这三个状态。
下面先来看一下其UML的类图:
抽象状态接口:State
public interface State { public void doJob(Washing washing); }
开始状态:Start
public class Start implements State { @Override public void doJob(Washing washing) { System.out.println("Start Washing Clothes!"); washing.setState(new Work()); washing.request(); } }
工作状态:Work
public class Work implements State{ @Override public void doJob(Washing washing) { System.out.println("Working Now!"); washing.setState(new End()); washing.request(); } }
结束状态:End
public class End implements State{ @Override public void doJob(Washing washing) { System.out.println("All Finished!"); washing.setState(null); } }
洗衣机类:Washing
public class Washing { private State state = null; public void setState(State state) { this.state = state; if (state == null) { System.out.println("Current state: null!"); } else { System.out.println("Current state: " + state.getClass().getName()); } } public void request() { if (state != null) { state.doJob(this); } } }
演示:
public class Demo { public static void main(String[] args) { Washing washing = new Washing(); washing.setState(new Start()); washing.request(); } }
结果:
Current state: state.Start Start Washing Clothes! Current state: state.Work Working Now! Current state: state.End All Finished! Current state: null!
washing中提供用户使用的主要接口。初始时,使用者使用一个状态来配置washing,然后便可对washing发送指令,后续不在需要用户直接于具体转态打交道。每个状态会自动控制向下一个状态转移,直到运行结束。
5. 总结
谈一下个人对于策略设计模式和状态模式的一些理解(不一定对,仅仅是一些思考,欢迎讨论):
5.1 策略模式:
a)频繁使用if else 可能严重消耗性能
策略模式比较适用于,行为类经常在某一个模式下工作,而不是会根据随机条件进行切换。
举个例子,在APP开发过程中,某一功能会依赖于横竖屏状态,那么我们是否需要在每一帧都是使用if else进行判断当前是横屏还是竖屏,然后进行下一步的处理?
显然这会严重消耗性能,正确的做法是将横竖屏处理拆分成两个策略,每次屏幕切换的时候,主动的切一下使用的模式;
b) 并不是所有的if else 和 分支都可以使用策略模式来替代
对于上面的压缩软件的例子,用户会选用一种模式,然后进行下面的工作,这个没问题。
但是如果我们提供的是一个压缩命令,该命令可以根据传递的参数,使用不同的压缩方式,那么使用if else就是必要的,因为我们不知道用户会输入什么参数,使用什么模式。
c) 策略模式没有策略
策略模式的核心是将一系列的操作拆分成可若干可单独重复使用的轮子,特定条件下直接选取其中一个使用,而不是传递条件,使用if else来进行条件判断以执行相应的操作。
从这个角度来看,策略模式名不副实,其不仅没有智能,合理的根据当前条件进行决策,还需要使用者主动的选取一种策略进行执行。这样做有好处,但同时其也变得更加没有策略。
实际开发过程中,我们都希望对方提供的接口简单好用,最好一个接口能搞定所有的问题,因为对于调用者而言,我并不关心你的实现,我只关心简单使用这个接口完成我的需求。
根本原因在于其破坏了封装性,暴露了具体的策略,这是其拆分组件便于扩展的同时带来的一个不可回避的问题,策略模式将决策由执行者提前到了调用者,代码灵活可扩展的同时带来的是使用的不便。
如果说策略模式主要是为了避免大量的if else决策,那么语言支持的话完全可以使用hashtable,分别以条件和函数对象作为key,value来直接根据条件选取对应的操作。对于大量分支尤其适用。
因此实际开发过程中需要根据自己的实际情况权衡利弊。
5.2 状态模式:
状态模式的核心是将对象每一个状态做的事情分别交给每一个单独的状态对象处理,并且由状态自己控制向其他状态的转移;行为类仅向外提供方便用户使用的接口;
对扩展状态不是特别友好,需要修改其他状态的转移。其次其实现比较灵活,用不好容易出错。
小结:
策略模式是通过 Context 本身的决策来主动更替使用的strategy对象达到改变行为的目的,状态模式通过状态转移来被动的更改当前的State对象,状态的改变发生在运行时。
策略模式提前封装一组可以互相替代的算法族,根据需要动态的选择合适的一个来处理问题,而状态模式处理不同状态下, Context 对象行为不同的问题;