了解策略模式和状态模式,并理解二者差异

策略模式和状态模式的代码结构非常相似,其UML类图更是一致,容易让人困惑。究其原因,是没有理解两种模式的设计目的,以至于明明设计了状态模式的代码结构,仍以策略模式的形式使用这些代码。

策略模式

策略模式比较简单,分析应用类,将类中用于完成特定任务的不同操作抽离成一组独立的类,称之为策略类。

由于不同操作的是服务同一任务的,其功能是一致的,只是具体实现不同,我们将相同的功能抽象为策略接口,所有策略类都通过实现这些接口来实现具体的操作;当然也可以抽离为抽象类,实现一些相同的操作,只抽象那些不同的操作。

这时候原来的应用类,只需要使用一致的策略接口就能完成特定任务,不需要维护具体的策略及其实现。

在客户端使用该类的时候,根据需求传入与需求对应的策略对象即可。在新增需求时,只需要新增对应的策略实现就能完成拓展,无需修改应用类和既有的策略类。

状态模式

状态模式的目的是让一个对象的内部状态变化时改变其行为,使其看上去就像改变了自身所属的类一样。

那些适用状态模式的对象,其操作所发生的行为由当前状态决定,且行为的发生可能会使对象的状态从当前状态转移到另一种状态。这就是有限状态机的概念。

简单地实现一个状态机,我们可能会使用 if/elseswitch/case 这样的条件运算符来判断状态、切换行为、转换状态。但是这会随着功能的膨胀使得代码难以维护。

状态的多寡与状态对应的行为会根据客户端需求的变化而变化,这也是造成代码维护困难的原因,将其分离出来即可解决。

策略模式建议将对象所有可能的状态都新建一个类,然后将状态所对应的行为抽离到该状态对应的类中。

状态模式包含以下主要角色:

  1. 环境类(Context)角色:也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。
  2. 抽象状态(State)角色:定义一个接口或抽象类,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。
  3. 具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需要的情况下通过持有环境类对象的引用来进行状态切换。

将受状态影响的操作抽离到抽象状态接口中,再由具体状态类实现不同的操作。
环境类角色通过依赖抽象状态接口来维护自身状态,不需要关心当前状态对应的具体行为,而是将所有与状态相关的工作都委派给该状态对象。

当我们需要拓展状态时,就新增一个状态类;当特定状态的行为发生改动时,只需要修改对应的状态类。环境类不需要进行改动。

但是,状态模式的弊端也很明显,它使类变多了。如果只是维护既有功能,它确实让代码变得清晰明了,易于维护。但是当我们的环境类新增了功能,且新增的功能受状态影响,那我们不得不修改抽象状态接口和所有的具体状态类。

理解策略模式与状态模式的区别

二者的代码组织方式非常相似,都有运行算法的环境类,而代表具体算法的策略接口、策略类以及状态接口、状态类更是一一对应。
但是其核心区别在于运行环境与算法、算法与算法之间的关系的不同,有不同关系是因为二者本质上是有着不同的目的。

  • 策略模式

    • 由客户端决定使用何种算法
    • 运行环境和具体算法之间没有感知,不同算法之间也没有感知
    • 运行环境单向依赖抽象的算法接口,算法不依赖运行环境,意味着算法可以被复用
    • 策略模式的目的是让客户端可以根据自身需求便捷地对同一任务指定不同算法,同时使新增需求的算法易于扩展,既有需求的算法易于维护。
  • 状态模式

    • 运行环境的算法由其内部状态决定
    • 运行环境对具体算法没有感知,但是算却可以通过转换运行环境的状态来实现算法的切换,所以算法对运行环境有感知,不同算法间也有感知
    • 算法往往依赖运行环境,算法除了转换运行环境的状态,还可能调用运行环境的服务方法,这意味着算法不能被复用
    • 状态模式的目地是为了使运行环境在其内部状态发生变化时能够自动地切换对应的算法,同时使新增的状态易于扩展,既有的状态易于维护。

虽然状态模式并未规定在哪里定义初始状态、在哪里转换状态。但是我觉得为了低耦合,客户端不应对环境对象进行任何状态对象的注入操作;初始的状态应在环境类内部创建并定义,而状态的转换可以在环境类或状态类中进行操作。

客户端不应指定状态模式中运行环境的状态(具体算法)。客户端调用环境对象的功能接口,可能间接地导致环境对象内部状态的转换,从而改变内部算法,但客户端对环境对象内部的变化是没有感知的。
如果非要在客户端进行状态的转换,这时候状态就变成了策略。若不能理解状态和运行环境是一个整体,写出来的状态模式的代码就和策略模式一样了。

posted @ 2024-02-07 23:03  钰琪  阅读(35)  评论(0编辑  收藏  举报