两种设计模式都封装了算法并将实现细节与其调用类分离。我能辨别的唯一区别是策略模式接受参数来执行,而命令模式没有。
在我看来,命令模式需要在创建时提供所有执行信息,并且它能够延迟其调用(可能作为脚本的一部分)。
哪些决定指导是否使用一种模式或另一种模式?
两种设计模式都封装了算法并将实现细节与其调用类分离。我能辨别的唯一区别是策略模式接受参数来执行,而命令模式没有。
在我看来,命令模式需要在创建时提供所有执行信息,并且它能够延迟其调用(可能作为脚本的一部分)。
哪些决定指导是否使用一种模式或另一种模式?
我包含了几个 GoF 设计模式的封装层次表,以帮助解释这两种模式之间的差异。希望它能更好地说明每个封装的内容,以便我的解释更有意义。
首先,层次结构列出了给定模式适用的范围,或用于封装某种详细程度的适当模式,具体取决于您从表格的哪一侧开始。
从表中可以看出,策略模式对象隐藏了算法实现的细节,因此使用不同的策略对象将执行相同的功能,但方式不同。每个策略对象都可能针对特定因素进行优化或对其他参数进行操作;并且,通过使用通用接口,上下文可以安全地使用其中任何一个。
与算法相比,命令模式封装的详细程度要小得多。它对向对象发送消息所需的细节进行编码:接收器、选择器和参数。将流程执行的这么小部分客观化的好处是,可以在不同的时间点或位置以一般方式调用此类消息,而无需对其细节进行硬编码。它允许消息被调用一次或多次,或者传递到系统的不同部分或多个系统,而不需要在执行之前知道特定调用的细节。
与设计模式的典型情况一样,它们并不要求所有实现在细节上都相同以带有模式名称。细节在实现以及在对象中编码的数据与作为方法参数的数据方面可能有所不同。
策略封装了算法。命令将请求的发送者与接收者分开,它们将请求转换为对象。
如果它是一种算法,那么将如何做某事,请使用策略。如果您需要将方法的调用与其执行分开,请使用命令。当您将消息排队以备后用时,通常会使用命令,例如任务或事务。
回答一个很老的问题。(有人看到最新的答案而不是投票最多的答案吗?)
由于相似之处,这是一个有效的混淆。策略和命令模式都使用封装。但这并不能使它们相同。
关键区别在于了解封装的内容。两种模式都依赖于 OO 原则,即封装变化的内容。
在策略的情况下,变化的是算法。例如,一个策略对象知道如何输出到 XML 文件,而另一个输出到 JSON。不同的算法被保存(封装)在不同的类中。它是如此简单。
在命令的情况下,不同的是请求本身。请求可能来自File Menu > Delete
orRight Click > Context Menu > Delete
或Just Delete Button pressed
。这三种情况都可以生成 3 个相同类型的命令对象。这些命令对象仅代表 3 个删除请求;不是删除算法。由于请求现在是一堆对象,我们可以轻松地管理它们。突然间,提供诸如撤消或重做之类的功能变得微不足道。
命令如何实现请求的逻辑并不重要。在调用 execute() 时,它可以实现一个算法来触发删除,甚至可以将其委托给其他对象,甚至可以委托给一个策略。它只是命令模式的实现细节。这就是为什么它被命名为命令的原因,尽管它不是一种礼貌的请求方式:--)
将其与战略进行对比;这种模式只关心被执行的实际逻辑。如果我们这样做,它有助于以最少的类集实现不同的行为组合,从而防止类爆炸。
我认为,Command 帮助我们拓宽了对封装的理解,而 Strategy 提供了对封装和多态的自然使用。
我的看法是,你有多种方式来做同样的事情,每一种都是一个策略,运行时的某些东西决定了哪个策略被执行。
也许先试试 StrategyOne,如果效果不够好,试试 StrategyTwo...
命令绑定到需要发生的不同事情,例如 TryToWalkAcrossTheRoomCommand。每当某个对象试图穿过房间时,都会触发此命令,但在其中,它可能会尝试使用 StrategyOne 和 StrategyTwo 来尝试穿过房间。
标记
我认为我可能是错的,但我将命令视为执行功能或反应。至少应该有两个玩家:一个请求动作,一个执行动作。GUI 是命令模式的典型示例:
该命令通常局限于某个范围或业务领域,但不是必需的:您可能有发出账单、启动火箭或删除execute()
在一个应用程序中实现相同接口(例如单一方法)的文件的命令。通常命令是自包含的,因此它们不需要执行器的任何东西来处理它们打算执行的任务(所有必要的信息都在构造时给出),有时命令是上下文敏感的,应该能够发现这个上下文(退格命令应该知道文本中插入符号的位置以正确删除前一个字符;回滚命令应该发现要回滚的当前事务;...)。
该策略有点不同:它更多地绑定到某个领域。该策略可以定义一个规则来格式化日期(在 UTC?特定于区域设置?)(“日期格式化程序”策略)或计算几何图形的正方形(“正方形计算器”策略)。从这个意义上说,策略是轻量级对象,它将某些东西作为输入(“日期”、“图形”……)并在其基础上做出一些决定。可能不是最好的,但很好的策略示例是与javax.xml.transform.Source
接口相关的策略:取决于传递的对象是DOMSource
或策略SAXSource
(StreamSource
在这种情况下为 XSLT 转换器)将应用不同的规则来处理它。实现可以是简单的switch
或涉及责任链模式。
但是这两种模式之间确实有一些共同点:命令和策略将算法封装在同一个语义区域内。
命令:
基本组件:
execute()
工作流程:
客户端调用Invoker => Invoker调用ConcreteCommand => ConcreteCommand调用Receiver方法,它实现了抽象的Command方法。
优点:客户端不受命令和接收器更改的影响。Invoker 提供 Client 和 Receiver 之间的松散耦合。您可以使用同一个 Invoker 运行多个命令。
命令模式允许您使用相同的Invoker在不同的Receiver上执行命令。调用者不知道接收者的类型
为了更好地理解概念,请查看Pankaj Kumar的JournalDev文章和James Sugrue的dzone文章以及Wikipedia 链接。
您可以使用命令模式
解耦命令的调用者和接收者
实现回调机制
实现撤消和重做功能
维护命令历史
java.lang.Thread
是命令模式的一种很好的实现。您可以将Thread视为调用者和将Runnable实现为ConcreteCommonad/Receiver的类,将run()
方法视为Command。
命令模式的撤消/重做版本可以在Theodore Norvell 的 文章中阅读
战略:
策略模式很容易理解。使用此模式时
您有多个算法实现,算法的实现可以在运行时根据特定条件而改变。
以航空公司预订系统的票价组件为例
航空公司希望在不同的时间段(高峰和非高峰月份)提供不同的票价。在非高峰旅行期间,它希望通过提供有吸引力的折扣来刺激需求。
策略模式的关键要点:
带有代码示例的相关帖子:
对我来说,区别在于意图之一。两种模式的实现非常相似,但目的不同:
对于策略,使用对象的组件知道对象做什么(并将使用它来执行它自己的一部分工作),但它并不关心它是如何做的。
对于命令,使用该对象的组件既不知道命令做什么也不知道它是如何做的——它只知道如何调用它。调用者的任务只是运行命令——命令执行的处理不构成调用者核心工作的一部分。
这就是区别——使用组件的对象是否真的知道或关心组件的功能?大多数情况下,这可以根据模式对象是否向其调用者返回值来确定。如果调用者关心模式对象的作用,那么它可能希望它返回一些东西,这将是一个策略。如果它不关心任何返回值,它可能是一个命令(注意,像 Java Callable 之类的东西仍然是一个命令,因为尽管它返回一个值,但调用者并不关心该值 - 它只是将它传回到最初提供命令的任何东西)。