策略设计模式和状态设计模式有什么区别?

272

策略模式与状态模式之间有什么区别?我查阅了很多文章,但是仍然不能清楚地区分它们的不同。

请问有人能用通俗易懂的语言来解释它们之间的区别吗?


4
根据这里的回答和我的观察,似乎_实现_在很大程度上是相同的(尽管不完全相同)。相反,差异主要在于意图:我们试图通过状态(状态模式)或其他基础(策略模式)来适应行为。很多时候,这个“其他基础”是“客户选择的内容”,通过注入来实现。 - Timo
22个回答

164

实际上,这两种模式在实践中非常相似,它们之间的定义差异往往取决于你问谁。一些流行的选择包括:

  • 状态存储对包含它们的上下文对象的引用,而策略则不是。
  • 状态允许自我替换(即将上下文对象的状态更改为其他内容),而策略则不允许。
  • 策略作为参数传递给上下文对象,而状态则由上下文对象本身创建。
  • 策略仅处理单个特定任务,而状态为上下文对象提供了基础实现,用于执行所有(或大多数)任务。

“经典”的实现方式将为列表中的每个项目匹配状态或策略,但您可能会遇到混合使用两者的情况。无论特定的实现是更加“状态”还是更加“策略”,最终都是主观问题。


7
如果你将《设计模式》一书视为流行选择之一,那么他们可能不同意状态必须由上下文创建(可以像策略模式一样由客户端创建并传递给上下文)。 - Will Hardwick-Smith

138
  • 策略模式实际上是指有不同的实现方式可以达到(基本上)相同的目标,因此一个实现可以根据需要替换另一个实现。比如说,在策略模式下你可以使用不同的排序算法,而对象的调用者并不会根据所采用的不同策略进行更改,但无论采用哪种策略,目标都是相同的(对集合进行排序)。
  • 状态模式是基于状态做不同的事情,同时使调用者免于适应每个可能的状态的负担。例如,你可以有一个getStatus()方法,该方法将根据对象的状态返回不同的状态值,但方法的调用者不必针对每个潜在的状态编写不同的代码。

2
但是在策略模式中,谁来改变策略呢? - Noor
1
通常,@Noor是某种参数或字段。实际调用者的代码不会因策略变化而改变。 - Yishai
4
@Noor,是的,但我现在能想到的任何策略模式都必须在一开始做出决定,中途不会改变。 - Yishai
3
我也遇到了同样的问题,状态或策略,我认为区别在于几个词,状态是自我决定的行为,而策略是由调用者决定的行为。 - Rene MF
1
在电子商务应用程序中,如果需要在节日季节上应用额外的折扣,则使用状态设计模式。如果有多种方法可以得出实际折扣率逻辑,则可以使用策略设计模式来应用该逻辑。 - Bharathkumar V
显示剩余4条评论

103

这两种模式的区别在于它们解决不同的问题:

  • State 模式处理对象所处的状态或类型,封装了与状态有关的行为,而
  • Strategy 模式处理对象执行某个任务的方法,封装了一个算法。

然而,实现这两种模式的构建方式非常相似;两种模式都是组合和委托的例子。


对它们优点的一些观察:

使用 State 模式可以使持有状态(上下文)的类从需要知道自己处于哪个状态或类型以及哪些状态或类型可用的知识中解脱出来。这意味着该类符合开闭原则(OCP):对于状态/类型的更改,类本身是关闭的,但是状态/类型是开放的,可以扩展。

使用 Strategy 模式可以使使用算法(上下文)的类从需要知道如何执行某个任务的知识中解脱出来。这也符合 OCP;该类对于如何执行此任务的更改是关闭的,但是设计非常开放,可以添加其他算法来解决此任务。
这可能还会提高上下文类对单一职责原则(SRP)的遵守度。此外,算法很容易被其他类重用。


61
我希望您能用通俗易懂的语言解释一下? 设计模式并不是“通俗易懂”的概念,但我会尽可能让它更清晰。任何设计模式都可以从三个维度考虑: 1. 模式解决的问题; 2. 模式的静态结构(类图); 3. 模式的动态行为(序列图)。 让我们来比较状态模式和策略模式。 模式解决的问题: 状态模式有两种情况[GoF book p. 306]: 1. 对象的行为取决于它的状态,并且必须根据该状态在运行时改变其行为。 2. 操作具有大型、多部分的条件语句,这些语句依赖于对象的状态。这个状态通常由一个或多个枚举常量表示。通常,几个操作将包含相同的条件结构。状态模式将条件的每个分支放在单独的类中。这使您可以将对象的状态视为一个独立于其他对象的对象,可以自行变化。
如果您想确保您确实有状态模式解决的问题,您应该能够使用有限状态机对对象的状态进行建模。您可以在这里找到一个应用示例here
每个状态转换都是State接口中的一个方法。这意味着对于设计,您必须非常确定状态转换,然后再应用此模式。否则,如果添加或删除转换,将需要更改接口和实现它的所有类。
我个人认为这种模式并不那么有用。您总是可以使用查找表来实现有限状态机(这不是面向对象的方式,但效果非常好)。
策略模式用于以下[GoF book p. 316]
  • 许多相关类的不同之处只在于它们的行为。策略提供了一种使用其中一种行为配置类的方法。
  • 您需要算法的不同变体。例如,您可能定义反映不同空间/时间权衡的算法。当这些变体实现为算法的类层次结构时,可以使用策略[H087]。
  • 算法使用客户端不应知道的数据。使用策略模式避免公开复杂的、特定于算法的数据结构。
  • 一个类定义了许多行为,并且这些行为出现在其操作中的多个条件语句中。将相关的条件分支移动到它们自己的策略类中,而不是有许多条件语句。

适用策略模式的最后一种情况与称为用多态替换条件表达式的重构相关。

总结:状态和策略解决非常不同的问题。如果您的问题不能用有限状态机建模,那么状态模式可能不合适。如果您的问题不涉及封装复杂算法的变体,则不适用策略。

模式的静态结构

状态(State)具有以下UML类结构:

PlantUML class diagram of State Pattern

策略具有以下UML类结构:

PlantUML class diagram of Strategy Pattern

摘要:就静态结构而言,这两种模式大多相同。事实上,诸如this one的模式检测工具认为“[...]这些模式的结构是相同的,禁止了它们在自动处理过程中(例如,不参考概念信息)的区分。
然而,如果ConcreteStates决定状态转换(请参见上图中的“might determine”关联),则可能存在重大差异。这会导致具体状态之间的耦合。例如(请参见下一节),状态A确定了转换到状态B。如果Context类决定转换到下一个具体状态,则这些依赖关系将消失。

模式的动态

如上文中的问题部分所述,状态(State)意味着行为随对象某些状态(state)在运行时而变化。因此,状态转换(transitioning)的概念适用于有限状态机的关系中所讨论的内容。[GoF]提到,转换可以在ConcreteState子类中定义,也可以在集中位置(例如基于表格的位置)中定义。
让我们假设一个简单的有限状态机:

PlantUML state transition diagram with two states and one transition

假设子类通过返回下一个状态对象来决定状态转换,那么动态过程看起来像这样:

PlantUML sequence diagram for state transitions

展示策略的动态,借用一个真实例子是很有用的。

PlantUML sequence diagram for strategy transitions

总结: 每个模式都使用多态调用根据上下文执行某些操作。在状态模式中,多态调用(转换)通常会导致下一个状态的更改。在策略模式中,多态调用通常不会改变上下文(例如,一次使用信用卡支付并不意味着下次会使用PayPal支付)。而状态模式的动态性取决于其对应的有限状态机,这对于正确应用该模式来说是必不可少的。

1
这个答案对我非常有帮助,让我区分了它们之间的差异。在我看来,状态机参数似乎是相关的。这实际上用计算机理论的方式总结了以上的答案。 - medunes
1
状态机和状态模式之间有所不同。在模式中,状态必须是多态的:每个状态都呈现相同的API。在机器中,转换到新状态可能会导致一组新操作。因此,该模式更注重设计状态内的行为,而机器更注重设计状态之间的转换。 - jaco0646

30
考虑一个处理客户呼叫的IVR(交互式语音应答)系统。您可能希望编程使其在以下情况下处理客户:
  • 工作日
  • 节假日
为了处理这种情况,您可以使用状态模式
  • 节假日:IVR简单地回复说“只能在工作日上午9点到下午5点接听电话”。
  • 工作日:它会将客户连接到客户服务主管。

将客户连接到支持主管的过程本身可以使用策略模式实现,其中根据以下任一选项选择支持主管:
  • 轮询
  • 最近最少使用
  • 其他基于优先级的算法
策略模式决定“如何”执行某些操作,而状态模式则决定“何时”执行它们。

这是一个非常好的答案,但是提到为什么在你的例子中需要许多算法会更有帮助。例如,算法是基于呼叫中心公司的偏好选择的。如果列表中有更简单或平凡的算法供那些不了解RR或LRU的人使用,那也会很有帮助。例如 - 长期客户获得更高的优先级,等待时间最长的客户获得更高的优先级。谢谢! - MasterJoe
哇!我真的推荐这个答案。一个当前模式,你在……通过算法执行计划! - user3833732

27

策略模式涉及将算法的实现从托管类中移出,放入一个单独的类中。这意味着宿主类不需要自己提供每个算法的实现,这很可能导致代码不清晰。

排序算法通常被用作示例,因为它们都做同样的事情(排序)。如果每种不同的排序算法都被放入其自己的类中,那么客户端就可以轻松地选择要使用的算法,而模式提供了一种简便的方法来访问它。

状态模式涉及在对象状态改变时更改对象的行为。这意味着宿主类不必为对象可能处于的所有不同状态提供行为的实现。宿主类通常封装一个类,该类在给定状态下提供所需的功能,并在状态更改时切换到另一个类。


16

策略模式代表着执行相同的开始和结束结果,但使用不同方法在内部“执行”某些操作的对象。在这个意义上,它们类似于表示动词的实现。另一方面,状态模式使用“是”某些东西的对象 - 操作的状态。虽然它们也可以表示对该数据的操作,但更类似于名词的表示而不是动词,并且专为状态机设计。


13

策略模式用于当你有多个针对特定任务的算法时,客户端决定在运行时使用哪个实际实现。

维基百科上的UML图表格策略模式文章:

enter image description here

关键特征:

  1. 它是一种行为模式。
  2. 它基于委托。
  3. 它通过修改方法行为来更改对象的核心部分。
  4. 它用于在算法族之间切换。
  5. 它在运行时更改对象的行为。

请参考此文章以获取更多信息和真实世界示例:

策略模式的真实世界示例

状态模式允许一个对象在其内部状态发生变化时改变其行为。

维基百科上的UML图表格状态模式文章:

enter image description here

如果我们需要根据对象的状态更改其行为,我们可以在对象中使用一个状态变量,并使用if-else条件块根据状态执行不同的操作。 状态模式用于通过上下文状态实现提供一种系统化和松耦合的方式来实现此目标。

更多细节请参考journaldev文章。

sourcemakingjournaldev文章的关键区别:

  1. 状态策略的不同之处在于绑定时间。 策略是一种一次性的模式,而状态更加动态
  2. 状态策略的差异在于意图。 使用策略时,算法选择相当稳定。 使用状态时,“上下文”对象状态的改变导致它从其“调色板”中选择策略对象
  3. 上下文将状态作为实例变量包含,可以有多个任务的实现取决于状态,而在策略模式中,策略作为参数传递给方法,而上下文对象没有任何变量来存储它。

12

策略模式:策略是固定的,通常由几个步骤组成。(排序只构成了一个步骤,因此是一个非常糟糕的例子,因为它太简单了,无法理解此模式的目的)。

策略中的“主”程序调用一些抽象方法。例如,“进入房间策略”,“主方法”是goThroughDoor(),看起来像这样:approachDoor(),如果(locked())openLock(); openDoor(); enterRoom(); turn(); closeDoor(); 如果(wasLocked())lockDoor()

现在,这个通用的从一个房间到另一个房间通过可能被锁的门移动的“算法”的子类可以实现算法的步骤。

换句话说,对策略进行子类化不会改变基本算法,只会改变个别步骤。

上述内容是模板方法模式。现在将属于同一步骤的步骤(解锁/锁定和打开/关闭)放入它们自己的实现对象中并委托给它们。例如,具有钥匙的锁和具有代码卡的锁是两种锁。从策略向“步骤”对象进行委派。现在,您就有了一个策略模式。

状态模式是完全不同的。

您有一个包装对象和被包装对象。被包装的对象是“状态”。状态对象只能通过其包装器访问。现在,您可以随时更改被包装的对象,因此包装器似乎更改了其状态,甚至更改了其“类”或类型。

例如,您有一个登录服务。它接受用户名和密码。它只有一个方法:logon(String userName,String passwdHash)。它不决定是否接受登录,而是将决策委托给状态对象。该状态对象通常只检查用户/密码组合是否有效并执行登录。但现在,您可以通过仅允许特权用户登录(例如,在维护时间期间)或通过不允许任何人登录来将“Checker”的更换。这意味着“checker”表示系统的“登录状态”。

最重要的区别是:一旦选择了策略,就要坚持到完成为止。这意味着你调用其“主方法”,只要该方法在运行,就不会更改策略。另一方面,在状态模式情况下,在系统运行时,您可以根据需要任意更改状态。


11

layman's language中,Strategy模式没有状态或所有状态都相同。 它只有不同的执行任务方式,就像不同的医生用不同的方式治疗同一个患者的同一种疾病。

在State模式中,主观上存在状态,例如患者的当前状态(例如高温或低温),基于此决定下一步的行动(药品处方)。 一个状态可以导致另一个状态,因此存在状态到状态的依赖关系(在技术上称为组合)。

如果我们从技术上理解它,根据两者的代码比较,我们可能会失去情境的主观性,因为两者看起来非常相似。


点赞!非常好的解释!谢谢! :) - StepUp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接