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

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

一、策略模式

关于策略模式,我之前写过一篇笔记,不过是 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();
    }
}

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

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();
    }
}

结果如下:

二、状态模式

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

状态模式简介

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

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

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

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

关键代码:状态模式的接口中通常有一个或者多个方法。而且,状态模式的实现类的方法,一般返回值,或者是改变实例变量的值。也就是说,状态模式一般和对象的状态有关。实现类的方法有不同的功能,覆盖接口中的方法。状态模式和策略模式一样,也可以用于消除 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();
    }
}

结果如下:

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

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

可以用多态来取代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();
    }
}

此时的执行结果如下:

四、总结

在现实世界中,策略和状态是两种完全不同的思想。虽然状态模式和策略模式拥有相似的结构,虽然它们都基于开闭原则,但是,它们的意图是完全不同的。当我们对状态和策略进行建模时,这种差异会导致完全不同的问题。对状态进行建模时,状态迁移是一个核心内容,状态模式帮助对象管理状态;而在选择策略时,状态迁移与此毫无关系。另外,策略模式允许一个客户选择或提供一种策略,而这种思想在状态模式中完全没有,所以在状态模式中,如果需要避免客户端的不安全操作,我们完全可以不提供代码中标记为 MARK1 的构造器,并将代码中标记为 MARK2 的方法私有化。

从代码上理解:是谁促使了行为的改变。状态模式中,状态转移由 Context 或 State 自己管理。如果你在State中管理状态转移,那么它必须持有Context的引用。例如,在上面代码中,StateB 的 function2() 方法需要调用 setState()方法去改变它的状态,它就需要传入一个 Context 类型参数。而策略模式中,Strategy 从不持有Context的引用,是客户端把所选择的 Strategy 传递给Context。由于状态模式和策略模式在工作中的使用场景比较多(我自己最近项目就有用到),所以本文重点分析记录状态模式和策略模式的异同,来加深我自己对它们的理解。也希望能帮助到有缘看到本文的朋友。

原文地址:https://www.cnblogs.com/joy99/p/9840715.html

时间: 2024-10-05 05:02:20

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

状态模式和策略模式区别

学完策略模式和状态模式后,对比发现两者的关系类图几乎一样,遂产生了疑问,这两者模式几乎一样的设计,那他两有什么区别吗 不过既然是两者设计模式,那他们肯定不一样了,哪怕是失散多年的孪生兄弟,那也只是同表不同里 下面结合书上和网上的博客记录下我的理解 策略模式类图: 状态模式类图: 策略模式:其思想是针对一组算法,将每一种算法都封装到具有共同接口的独立的类中,从而是它们可以相互替换.策略模式的最大特点是使得算法可以在不影响客户端的情况下发生变化,从而改变不同的功能 状态模式:允许一个对象在其内部状态

工厂模式与策略模式之区别

设计模式有很多种,其中功能相似的很多,但是为什么还要分这么多种名字,查阅资料,我觉得下面的解释最为合理:用途不一样,名字就有区别,一把斧头用来砍人就叫凶器,用来砍柴就叫伐木斧,用来劈门就叫消防斧,这些模式的名字都是根据具体使用时的场景,联系了现实里某样东西或某种习惯而取得,所以很相似的模式行为有不同叫法. 今天我们就来研究一些工厂模式与策略模式的一些区别: 工厂模式是创建型模式,适应对象的变化. 策略模式是行为性模式,适应行为的变化 工厂模式封装对象,实例化对象后调用的时候要知道具体的方法,策略

设计模式之桥梁模式和策略模式的区别

桥接(Bridge)模式是结构型模式的一种,而策略(strategy)模式则属于行为模式.以下是它们的UML结构图. 桥梁模式: 策略模式: 在桥接模式中,Abstraction通过聚合的方式引用Implementor. 举一个例子: 策略模式:我要画圆,要实心圆,我可以用solidPen来配置,画虚线圆可以用dashedPen来配置.这是strategy模式. 桥接模式:同样是画圆,我是在windows下来画实心圆,就用windowPen+solidPen来配置,在unix下画实心圆就用uni

简单工厂、工厂方法、抽象工厂、策略模式、策略与工厂的区别

结合简单示例和UML图,讲解工厂模式简单原理. 一.引子 话说十年前,有一个爆发户,他家有三辆汽车(Benz(奔驰).Bmw(宝马).Audi(奥迪)),还雇了司机为他开车.不过,爆发户坐车时总是这样:上Benz车后跟司机说"开奔驰车!",坐上Bmw后他说"开宝马车!",坐上 Audi后他说"开奥迪车!".你一定说:这人有病!直接说开车不就行了?!而当把这个爆发户的行为放到我们程序语言中来,我们发现C语言一直是通过这种方式来坐车的!幸运的是这种有

从桥接模式与策略模式谈起(转载)

原文地址:http://www.blogjava.net/wangle/archive/2007/04/25/113545.html 从桥接模式与策略模式谈起 桥接(Bridge)模式是结构型模式的一种,而策略(strategy)模式则属于行为模式.以下是它们的UML结构图. 在桥接模式中,Abstraction通过聚合的方式引用Implementor. 在策略模式中,Context也使用聚合的方式引用Startegy抽象接口. 从他们的结构图可知,在这两种模式中,都存在一个对象使用聚合的方式引

设计模式之_简单工厂模式、工厂方法模式、抽象工厂模式 、策略模式、策略与工厂的区别(转)

一.前言 话说十年前,有一个爆发户,他家有三辆汽车(Benz(奔驰).Bmw(宝马).Audi(奥迪)),还雇了司机为他开车.不过,爆发户坐车时总是这样:上Benz车后跟司机说“开奔驰车!”,坐上Bmw后他说“开宝马车!”,坐上 Audi后他说“开奥迪车!”.你一定说:这人有病!直接说开车不就行了?!而当把这个爆发户的行为放到我们程序语言中来,我们发现C语言一直是通过这种方式来坐车的 幸运的是这种有病的现象在OO语言中可以避免了.下面以Java语言为基础来引入我们本文的主题:工厂模式! 二.简介

命令模式与策略模式之己见

以前项目写过关于TR069协议报文处理的代码(主要是基于SOAP协议发送一些远程命令并处理响应),在设计的时候,想的是应用策略模式对报文进行解析处理, 下图是主要代码结构(和策略模式很像) 代码类似于: /** * 1.需要解析的XML */ String xml = "<xml>...</xml>"; /** * 2.获取xml类型 */ MessageType type = SoapMessageFactory.getRpcType(xml); /** *

Android设计模式之命令模式、策略模式、模板方法模式

命令模式是其它很多行为型模式的基础模式.策略模式是命令模式的一个特例,而策略模式又和模板方法模式都是算法替换的实现,只不过替换的方式不同.下面来谈谈这三个模式. 命令模式 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化:对请求排队或记录请求日志,以及支持可撤消的操作. java中传递(注入)对象很容易,但是却不支持直接传递行为(即传递函数或者说传递方法),只能间接的通过传递(注入)一个对象,再调用它的行为来实现.如果把这样的行为抽取出来为一个类,称作命令类,它的具体实现都是命令

设计模式之桥梁模式和策略模式的差别

桥接(Bridge)模式是结构型模式的一种,而策略(strategy)模式则属于行为模式.下面是它们的UML结构图. 桥梁模式: 策略模式: 在桥接模式中,Abstraction通过聚合的方式引用Implementor. 举一个样例: 策略模式:我要画圆.要实心圆,我能够用solidPen来配置.画虚线圆能够用dashedPen来配置. 这是strategy模式. 桥接模式:相同是画圆,我是在windows下来画实心圆.就用windowPen+solidPen来配置.在unix下画实心圆就用un