策略模式和状态模式的代码实现方式很类似,但是两者又有本质区别,我们将两种模式放在一起讲解,以便比较两者的异同。
*
策略模式
1.解决的问题
设计模式都是为解决某种问题而产生的。策略模式解决的问题就是:当我们的代码里有很多的if…else if…语句时,且每个条件都是独立的算法,没有相互关联时,我们可以考虑用策略模式,来拆解臃肿的if…else语句,降低代码耦合度。
2.实现方式
我们将每个条件判断语句内的算法都定义成一个策略,不同的策略实现同一个策略接口,以保证统一。在if…else…的条件判断模块,我们以传入不同的参数的方式,传入不同的策略类,去执行不同的算法。以此减少臃肿的条件判断。
3.角色
Context(环境类):环境类是使用算法的角色,它在解决某个问题(即实现某个方法)时可以采用多种策略。在环境类中维持一个对抽象策略类的引用实例,用于定义所采用的策略。就是写if…else…语句的类,把if…else…抽取成了策略,所以就是使用策略的类。
Strategy(抽象策略类):它为所支持的算法声明了抽象方法,是所有策略类的父类,它可以是抽象类或具体类,也可以是接口。环境类通过抽象策略类中声明的方法在运行时调用具体策略类中实现的算法。
ConcreteStrategy(具体策略类):它实现了在抽象策略类中声明的算法,在运行时,具体策略类将覆盖在环境类中定义的抽象策略类对象,使用一种具体的算法实现某个业务处理。
4.代码实现
策略模式的实现很简单,我们先看伪代码的实现:
抽象策略类:
//抽象策略类,定义统一的算法规范
public interface AbstractStrategy {
public void strategy();
}
然后,我们定义具体策略类,实现不同的策略算法:
public class ConcreteStrategy1 implements AbstractStrategy{
@Override
public void strategy() {
System.out.println("执行算法1");
}
}
public class ConcreteStrategy2 implements AbstractStrategy {
@Override
public void strategy() {
System.out.println("执行算法2");
}
}
最后,我们定义环境类,来使用这些策略:
//环境类
public class Context {
//不使用策略模式的写法:使用if..else..判断情况,并写每个情况的逻辑
public void notUserStrategy(String strategy){
if("情况一".equals(strategy)){
System.out.println("执行情况一的逻辑");
}else if("情况二".equals(strategy)){
System.out.println("执行情况二的逻辑");
}else if("情况三".equals(strategy)){
System.out.println("执行情况三的逻辑");
}else{
System.out.println("执行很多情况的逻辑");
}
}
//使用策略类模式:传入不同的策略类,调用统一的策略方法,执行不同的情况
public void useStrategy(AbstractStrategy strategy){
strategy.strategy();
}
}
由上面的比较我们可以得知,使用了策略模式,在环境类里,我们可以很方便的调用不同的情况。但是每种情况,都要对应一个具体的策略实现类,所以,当有很多很多种情况时,我们也要斟酌一下,是否建立很多种具体策略类呢?
下面,我们看策略模式在JDK中的应用。
在Comparator接口中,定义了compare()方法规范,这个就是抽象策略类角色,规定了比较策略的统一方法名称。
具体的策略实现类,由程序员实现Comparator接口进行实现。环境类就是Arrays.sort()方法,该重载方法之一就是传入不同的Comparator对象,来实现不同的排序方式。这应用的就是策略模式思想:传入不同的参数,执行不同的策略。
5.策略模式优缺点
优点:
- 策略模式提供了对 “开闭原则” 的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
- 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族,恰当使用继承可以把公共的代码移到抽象策略类中,从而避免重复的代码。
- 策略模式提供了一种可以替换继承关系的办法。如果不使用策略模式而是通过继承,这样算法的使用就 和算法本身混在一起,不符合
- “单一职责原则”,而且使用继承无法实现算法或行为在程序运行时的动态切换。
- 使用策略模式可以避免多重条件选择语句。多重条件选择语句是硬编码,不易维护。
- 策略模式提供了一种算法的复用机制,由于将算法单独提取出来封装在策略类中,因此不同的环境类可以方便地复用这些策略类。
缺点:
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
- 策略模式将造成系统产生很多具体策略类,任何细小的变化都将导致系统要增加一个新的具体策略类。
- 无法同时在客户端使用多个策略类,也就是说,在使用策略模式时,客户端每次只能使用一个策略类,不支持使用一个策略类完成部分功能后再使用另一个策略类来完成剩余功能的情况。
状态模式
1.解决的问题
策略模式我们讲到,是为了解决很多if…else if…而产生的设计模式。状态模式也一样,也是为了解决这个问题。两者的区别是:策略模式中,各个逻辑判断分支里的策略是相互独立的,没有关系的。而在状态模式中,每个逻辑判断分支里的状态,是要切换成其他逻辑判断分支里的状态的,体现的是一个状态的切换,所以在状态模式中,各个逻辑判断分支是有相互关系的。
2.实现方式
状态模式的实现方式和策略模式的很类似。也是将if…else中的逻辑先提取成一个抽象规范,然后再由具体的实现类去实现不同的逻辑分支。我们上面讲到,状态模式中各个判断分支是有关系的,他们需要相互切换成其他分支的状态。所以,这就要求我们在用该设计模式时,必须知道它有几种状态,我们才能切换到其他的状态。从这点来说,如果加入一个新状态的话,是需要修改实现类的代码的,对开闭原则的支持并不是很友好。
3.角色
环境类(Context)角色:也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。
抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。
具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。
我们可以看到,状态模式中,比策略模式多的就是状态的切换。
4.代码实现
我们利用状态模式,来简单模拟一下线程的状态切换。
我们知道线程有新建状态(NEW)、 可运行(RUNNABLE)、 运行(RUNNING)、阻塞(BLOCKED)、死亡(DEAD)5中状态,首先,我们创建环境类MyThread:
//环境类,切换线程状态
public class MyThread {
private State state;//记录当前状态
//在环境类中,需要初始化一种状态,以便于后续对状态进行切换,这也是状态模式的特点
public MyThread()
{
this.state=new NewState();
}
//获取当前状态
public State getState(){
return state;
}
//切换当前状态
public void setState(State state){
this.state=state;
}
//业务处理方法:传入不同的状态,进行切换
public void handle(State state){
state.handle(this);
}
}
然后,我们定义抽象状态角色:
public abstract class State {
private String statename;//状态名称
//处理业务逻辑,切换状态。因为切换的是环境类的状态,所以需要传入环境类
public abstract void handle(MyThread myThread);
//设置状态名称
public void setStatename(String name){
this.statename=name;
}
//获取状态名称
public String getStatename(){
return statename;
}
}
然后,我们定义具体的状态角色,这里我们只写三种角色:
//具体角色类:新建状态
public class NewState extends State {
@Override
public void setStatename(String name) {
super.setStatename("新建状态");
}
@Override
public void handle(MyThread myThread) {
System.out.println("当前状态为"+getStatename());
//切换状态
myThread.setState(new RunningState());
}
}
//具体状态类:运行状态
public class RunningState extends State {
@Override
public void setStatename(String name) {
super.setStatename("运行状态");
}
@Override
public void handle(MyThread myThread) {
System.out.println("当前状态为"+getStatename());
//切换状态
myThread.setState(new DeadState());
}
}
//具体状态类:死亡状态
public class DeadState extends State {
@Override
public void setStatename(String name) {
super.setStatename("死亡状态");
}
@Override
public void handle(MyThread myThread) {
System.out.println("当前状态为:"+myThread.getState());
//不再切换状态
}
}
这样,一个状态模式,就完成了。由此我们可以体会到,状态模式中,我们必须提前知道有哪些模式,且这些模式不会再改变,否则,会引起很大的代码改动。
状态模式用在有明显的状态变化和切换的场景中。
5.状态模式与责任链模式的区别
上面我们重点强调了状态模式和策略模式的区别,不知道大家有没有发现,状态模式和责任链模式,也有些相似,都是处理完之后,交给了下一个去处理。两者的本质区别是,状态模式中的状态必须是提前确定好的,且在设计模式中就已经确定了下一个状态的顺序。而责任链模式,只定义了节点规范,下一个节点进入哪个,由客户端自己决定。责任链模式文章请看《设计模式之责任链模式(Chain of Responsibility Pattern)》