孪生兄弟状态模式与策略模式有什么区别,究竟该如何选择

都说状态模式和策略模式很像,它们的 UML 类图同样。这也说明,单纯从代码角度来说,它们的本质同样,其实都是多态的应用。但它们实际所表明的的事物特征是有本质区别的,选择哪一个设计模式,表明了你看待业务场景的角度。从合理角度地对业务进程抽象,选择恰当的设计模式,才能让代码有更好的结构。
这篇文章重点说说我对状态模式和策略模式区别的理解,以及如何选择。html

1、策略模式

关于策略模式,我以前写过一篇笔记,不过是 C# 写的。策略模式解决了代码逻辑分支较多,对不一样的分支,采起不一样措施的问题。不熟悉策略模式的,也能够上集回顾: 扯一扯 C#委托和事件?策略模式?接口回调?算法

策略模式简介

在策略模式(Strategy Pattern)中,一个类的行为或其算法能够在运行时更改。咱们建立表示各类策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。这种类型的设计模式属于行为型模式。设计模式

意图:定义一系列的算法,把它们一个个封装起来, 而且使它们可相互替换。
主要解决:在有多种算法类似的状况下,使用 if...else 所带来的复杂和难以维护。
什么时候使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。
如何解决:将这些算法封装成一个一个的类,任意地替换。
关键代码:实现同一个接口。安全

策略模式的模型代码

  • 策略的抽象,定一个策略接口,声明不一样策略方案所需实现的方法:
public interface Stragety {
    void function();
}
  • 具体的策略类,定义不一样的策略类,实现策略抽象接口:
public class StrategyA implements Stragety {

    @Override
    public void function() {
        System.out.println("invoke StrategyA function ...");
    }
}
public class StrategyB implements Stragety {
    @Override
    public void function() {
        System.out.println("invoke StrategyB function ...");
    }
}
  • 操做策略的上下文环境,Context 类:
public class Context {
    private Stragety stragety;

    public Context() {
    }

    public Context(Stragety stragety) {
        this.stragety = stragety;
    }

    public void setStragety(Stragety stragety) {
        this.stragety = stragety;
    }

    public void function() {
        if (stragety == null) {
            System.out.println("not set strategy...");
            return;
        }
        stragety.function();
    }
}

最后调用的测试代码以下:ide

public class Test {
    public static void main(String[] args) {
        Context context = new Context();
        context.function();
        context.setStragety(new StrategyA());
        context.function();
        context.setStragety(new StrategyB());
        context.function();
    }
}

结果以下:工具

2、状态模式

状态模式中的行为是由状态来决定的,在状态模式(State Pattern)中,类的行为是基于它的状态改变的。咱们建立表示各类状态的对象和一个行为随着状态对象改变而改变的 context 对象。这种类型的设计模式也属于行为型模式。测试

状态模式简介

意图:容许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。this

主要解决:对象的行为依赖于它的状态(属性),而且能够根据它的状态改变而改变它的相关行为。设计

什么时候使用:代码中包含大量与对象状态有关的条件语句。code

如何解决:将各类具体的状态类抽象出来。

关键代码:状态模式的接口中一般有一个或者多个方法。并且,状态模式的实现类的方法,通常返回值,或者是改变实例变量的值。也就是说,状态模式通常和对象的状态有关。实现类的方法有不一样的功能,覆盖接口中的方法。状态模式和策略模式同样,也能够用于消除 if...else 等条件选择语句。

状态模式的模型代码

  • 状态的抽象,定一个状态接口,声明不一样状态下所需实现的方法:
public interface State {
    void function1();

    void function2();
}
  • 具体的状态类,定义不一样的状态类,实现状态抽象接口:
public class StateA implements State {
    @Override
    public void function1() {
        System.out.println("invoke StateA function1 ...");
    }

    @Override
    public void function2() {
        System.out.println("invoke StateA function2 ...");
    }
}
public class StateB implements State {
    @Override
    public void function1() {
        System.out.println("invoke StateB function1 ...");
    }

    @Override
    public void function2() {
        System.out.println("invoke StateB function2 ...");
    }
}
  • 维护状态的上下文环境,Context 类:
public class Context {
    private State state;

    public Context() {
    }

    public Context(State originalState) {
        this.state = originalState;
    }

    public void setState(State state) {
        this.state = state;
    }

    public void setStateA() {
        setState(new StateA());
    }

    public void setStateB() {
        setState(new StateB());
    }

    public void function1() {
        state.function1();
    }

    public void function2() {
        state.function2();
    }
}

最后调用的测试代码以下:

public class Test {
    public static void main(String[] args) {
        Context context = new Context();
        context.setStateA();
        context.function1();
        context.function2();
        context.setStateB();
        context.function1();
        context.function2();
    }
}

结果以下:

3、状态模式和策略模式的区别

经过上面模型代码的对比,有的同窗可能发现了,乍一看代码,其实二者几乎没有没什么区别,都是在玩多态的语法糖。这让我想起了经典书籍《重构,改善既有代码的设计》第一章中的那个例子,重构篇中有以下一段话——

能够用多态来取代switch语句,可是由于:一部影片能够在生命周期内修改本身的分类,一个对象却不能在生命周期内修改本身所属的类。因此这里不能用策略模式,用多态取代switch,而应该用状态模式(State)。       
                                 
这是一个State模式仍是一个Strategy模式?答案取决于Price类究竟表明计费方式,仍是表明影片的某个状态。

也就是说对于一个场景的抽象选择策略模式仍是状态模式,取决于你对这个场景的认知。个人理解是策略,即方法,不一样的策略实现类中相同的方法,地位应该是平等的。举几个例子,早餐选择吃面包,中餐选择吃米饭,这是策略上的决定;交通工具是选择乘坐公交车仍是乘坐地铁;在中国选择说中文,在美国选择说英语...

而状态之间,每每伴随着转换,即状态迁移,多是在时序上的状态迁移,也多是在某个状态上执行某种行为(调用某个方法)的时候,转化为另一种状态。它的重点应该是状态迁移,譬如烧水过程当中,水温能够当作状态;手机移动数据蓝牙WiFi的开关、汽车行驶的速度;在中国的时候说中文,在美国的时候说英语...均可以抽象成状态。咦,等等!!!在中国选择说中文,在美国选择说英语,到底抽象成策略模式仍是状态模式?

其实这就仍是要看你是怎么看待这个场景了,你要把它当作两中平等的场景,只是在不一样的场景中,作一个选择,则能够抽象成策略模式,若是你把在中国和在美国当作两种状态,而且两种状态能够发生转换,好比处于在中国这种状态下,有一个搭乘飞机飞到了中国的行为,状态变成了在中国,此时就应该考虑抽象成状态模式。

  1. 状态转换场景中,最好不要由客户端来直接改变状态(也不是绝对不能够),而是客户端作了某种其它操做引发状态迁移,也就是说客户端最好不要直接建立一个具体 State 实现类的实例对象,经过 setState() 方法来设置。
  2. 状态转换也多是对象的内部行为形成的。

这么一想,状态模式和策略模式代码实现仍是有区别的,下面我结合以上想法将状态模式模型代码作修改。
针对第一点,好比,客户端执行了 actionB 方法方法,使得状态改变成 StateB , 针对第二点,假设,在状态 StateB 中,执行 function2 的时候,会切换状态为 StateA。此时代码应该是这样的:

  • 定一个状态接口:
public interface State {
    void function1();

    void function2(Context context);
}
  • 具体的状态类:
public class StateA implements State {
    @Override
    public void function1() {
        System.out.println("invoke StateA function1 ...");
    }

    @Override
    public void function2(Context context) {
        System.out.println("invoke StateA function2 ...");
    }
}
public class StateB implements State {
    @Override
    public void function1() {
        System.out.println("invoke StateB function1 ...");
    }

    @Override
    public void function2(Context context) {
        System.out.println("invoke StateB function2 ...");
        context.setStateA();
    }
}
  • 增长一个可能在其它操做中改变状态的方法,封装成接口:
public interface Others {
    void actionB();
}
  • 维护状态的上下文环境,Context 类:
public class Context implements Others{
    private State state;

    public Context() {
    }

    // 为了方便下文说明,标记为--------MARK1
    public Context(State originalState) {
        this.state = originalState;
    }

    // 为了方便下文说明,标记为--------MARK2
    public void setState(State state) {
        this.state = state;
    }

    public void setStateA() {
        setState(new StateA());
    }

    public void setStateB() {
        setState(new StateB());
    }

    public void function1() {
        state.function1();
    }

    public void function2() {
        state.function2(this);
    }

    @Override
    public void actionB() {
        System.out.println("invoke actionB ...");
        setStateB();
    }
}

最后调用的测试代码以下:

public class Test {
    public static void main(String[] args) {
        Context context = new Context();
        context.setStateA();
        context.function1();
        context.actionB();
        context.function1();
        context.function2();
        context.function1();
        context.function2();
    }
}

此时的执行结果以下:

4、总结

在现实世界中,策略和状态是两种彻底不一样的思想。虽然状态模式和策略模式拥有类似的结构,虽然它们都基于开闭原则,可是,它们的意图是彻底不一样的。当咱们对状态和策略进行建模时,这种差别会致使彻底不一样的问题。对状态进行建模时,状态迁移是一个核心内容,状态模式帮助对象管理状态;而在选择策略时,状态迁移与此毫无关系。另外,策略模式容许一个客户选择或提供一种策略,而这种思想在状态模式中彻底没有,因此在状态模式中,若是须要避免客户端的不安全操做,咱们彻底能够不提供代码中标记为 MARK1 的构造器,并将代码中标记为 MARK2 的方法私有化。 从代码上理解:是谁促使了行为的改变。状态模式中,状态转移由 Context 或 State 本身管理。若是你在State中管理状态转移,那么它必须持有Context的引用。例如,在上面代码中,StateB 的 function2() 方法须要调用 setState()方法去改变它的状态,它就须要传入一个 Context 类型参数。而策略模式中,Strategy 从不持有Context的引用,是客户端把所选择的 Strategy 传递给Context。因为状态模式和策略模式在工做中的使用场景比较多(我本身最近项目就有用到),因此本文重点分析记录状态模式和策略模式的异同,来加深我本身对它们的理解。也但愿能帮助到有缘看到本文的朋友。

相关文章
相关标签/搜索