【问题标题】:Using a strategy pattern and a command pattern使用策略模式和命令模式
【发布时间】:2011-04-22 11:20:37
【问题描述】:

这两种设计模式都封装了一个算法,并将实现细节与其调用类分离。我能看出的唯一区别是 Strategy 模式接受参数来执行,而 Command 模式没有。

在我看来,命令模式要求所有执行信息在创建时都可用,并且能够延迟调用(可能作为脚本的一部分)。

什么决定指导是使用一种模式还是另一种模式?

【问题讨论】:

    标签: design-patterns encapsulation strategy-pattern command-pattern


    【解决方案1】:

    我包含了几个 GoF 设计模式的封装层次表,以帮助解释这两种模式之间的差异。希望它能更好地说明每个封装的内容,以便我的解释更有意义。

    首先,层次结构列出了给定模式适用的范围,或用于封装某种详细程度的适当模式,具体取决于您从表格的哪一侧开始。

    从表中可以看出,策略模式对象隐藏了算法实现的细节,因此使用不同的策略对象将执行相同的功能,但方式不同。每个策略对象都可能针对特定因素进行优化或对其他参数进行操作;并且,通过使用通用接口,上下文可以安全地使用其中任何一个。

    与算法相比,命令模式封装的详细程度要小得多。它对向对象发送消息所需的细节进行编码:接收器、选择器和参数。将流程执行的这么小部分客观化的好处是,可以在不同的时间点或位置以一般方式调用此类消息,而无需对其细节进行硬编码。它允许消息被调用一次或多次,或者传递到系统的不同部分或多个系统,而不需要在执行之前知道特定调用的细节。

    作为典型的设计模式,它们并不要求所有实现在细节上都相同以使用模式名称。实现细节以及在对象中编码的数据与作为方法参数的数据可能有所不同。

    【讨论】:

    • 那么,如果我有一个系统使用“过滤器管道”过滤结果并将委托用作过滤器(其中每个过滤器的算法都将封装在一个函数中),那会被视为命令模式吗?在这种情况下,我认为过滤器功能的委托为每个过滤器在输入和输出方面必须遵守的内容提供了一种契约。
    • @KTF,没有。命令模式使用一个对象,该对象具有调用对象方法所需的大部分(如果不是全部)信息(例如,接收器、选择器、参数)。这是一种简单的模式,可用于其他设计模式,如责任链、集合和您描述的管道模式。您的代表提供的“各种合同”是另一种模式,接口。
    【解决方案2】:

    策略封装了算法。命令将请求的发送者与接收者分开,它们将请求转换为对象。

    如果是算法,如何做某事,请使用策略。如果您需要将方法的调用与其执行分开,请使用命令。当您将消息排队以备后用时,通常会使用命令,例如任务或事务。

    【讨论】:

    【解决方案3】:

    回答一个非常古老的问题。 (有人看到最新的答案而不是投票最多的答案吗?)

    由于相似之处,这是一个有效的混淆。策略和命令模式都使用封装。但这并不能使它们相同。

    关键的区别在于理解什么被封装了。两种模式所依赖的 OO 原则是封装变化的内容

    就策略而言,变化的是算法。例如,一个策略对象知道如何输出到 XML 文件,而另一个输出到例如 JSON。不同的算法被保存(封装)在不同的类中。就这么简单。

    在命令的情况下,变化的是请求本身。请求可能来自File Menu > DeleteRight Click > Context Menu > DeleteJust Delete Button pressed。这三种情况都可以生成 3 个相同类型的命令对象。这些命令对象仅代表 3 个删除请求;不是删除算法。由于请求现在是一堆对象,我们可以轻松地管理它们。突然之间,提供撤消或重做等功能变得微不足道。

    命令如何实现请求的逻辑并不重要。在调用 execute() 时,它可以实现一个算法来触发删除,或者它甚至可以将其委托给其他对象,甚至可以委托给一个策略。它只是命令模式的实现细节。这就是为什么它被命名为 command 虽然它不是一种礼貌的方式 request :--)

    将其与策略进行对比;这个模式只关心被执行的实际逻辑。如果我们这样做,它有助于以最少的类集实现不同的行为组合,从而防止类爆炸。

    我认为,Command 帮助我们拓宽了对封装的理解,而 Strategy 则提供了对封装和多态性的自然使用。

    【讨论】:

      【解决方案4】:

      我的看法是,你有多种方式来做同样的事情,每一种都是一个策略,运行时的某些东西决定了哪个策略会被执行。

      不妨先试试 StrategyOne,如果效果不够好,试试 StrategyTwo...

      命令绑定到需要发生的不同事情,例如 TryToWalkAcrossTheRoomCommand。每当某个对象试图穿过房间时,都会触发此命令,但在其中,它可能会尝试使用 StrategyOne 和 StrategyTwo 来尝试穿过房间。

      标记

      【讨论】:

      • RE:“做同一件事的多种方式” - 这似乎与一些常见的策略示例相冲突。特别是那些有执行加法,减法,乘法等的实现类。也许这些不是很好的例子?
      • @JoshuaDavis 所有这些“子策略”都是一种策略的特例:算术运算。它们有共同的参数(2 个操作数)并产生一个值作为结果。几乎以自己不同的方式做同样的事情(作为黑盒),具体取决于实现。所以我在这里没有看到冲突,但是恰恰相反:很好的例子=)
      【解决方案5】:

      我的观点可能是错误的,但我将command 视为执行功能或反应。至少应该有两个玩家:一个请求动作,一个执行动作。 GUI 是命令模式的典型示例:

      • 应用程序工具栏上的所有按钮都与某些操作相关联。
      • Button 是本例中的执行者。
      • 在这种情况下,操作就是命令。

      该命令通常限于某个范围或业务领域,但不是必需的:您可能有一些命令可以在一个应用程序中发出账单、启动火箭或删除实现相同接口(例如单个execute() 方法)的文件.通常命令是自包含的,因此它们不需要执行器的任何东西来处理它们打算执行的任务(所有必要的信息都在构造时给出),有时命令是上下文敏感的,应该能够发现这个上下文(Backspace 命令应该知道文本中插入符号的位置以正确删除前一个字符;Rollback 命令应该发现当前事务以回滚;...)。

      strategy 有点不同:它更多地绑定到某个区域。该策略可以定义一个规则来格式化日期(在 UTC?特定于区域设置?)(“日期格式化程序”策略)或计算几何图形的正方形(“正方形计算器”策略)。从这个意义上说,策略是轻量级对象,它将某些东西作为输入(“日期”、“图形”……)并在其基础上做出一些决定。也许不是最好的,但是一个很好的策略示例是与javax.xml.transform.Source 接口连接的策略:取决于传递的对象是DOMSourceSAXSource 还是StreamSource,策略(在这种情况下为XSLT 转换器)将应用不同的处理它的规则。实现可以是简单的switch,也可以涉及Chain of responsibility pattern

      但这两种模式确实有一些共同点:命令和策略将算法封装在同一个语义区域内。

      【讨论】:

      • 我将命令视为回调函数或反应。至少应该有两个玩家:一个请求行动,一个执行...... - 我明白你想说什么,但我会回避使用“回调”这个词,因为“回调”这个词通常意味着异步调用,并且您不需要进行异步调用以使命令模式有用。恰当的例子:Microsoft Word。工具栏按钮单击和快捷键按下不会调用异步命令,但我们可以理解命令模式在这种情况下如何有用
      • 我同意,尽管正如 Jim 所说,我会编辑以删除对回调的引用。
      • 谢谢,我做了一些扩展。如果您同意/不同意,请告诉我。
      【解决方案6】:

      命令:

      基本组件:

      1. Command 为抽象命令声明一个接口,例如 execute()
      2. 接收者知道如何执行特定命令
      3. Invoker 持有 ConcreteCommand,必须执行
      4. Client 创建 ConcreteCommand 并分配 Receiver
      5. ConcreteCommand 定义 CommandReceiver 之间的绑定

      工作流程:

      Client 调用 Invoker => Invoker 调用 ConcreteCommand => ConcreteCommand 调用Receiver方法,实现抽象的Command方法。

      优势:客户端不影响命令和接收器的更改。 Invoker 提供 Client 和 Receiver 之间的松散耦合。您可以使用同一个 Invoker 运行多个命令。

      Command 模式允许您使用相同的Invoker 在不同的Receiver 上执行命令。 Invoker 不知道 Receiver

      的类型

      为了更好地理解概念,除了维基百科链接之外,请查看 Pankaj Kumar 的 JournalDev articleJames Sugrue 的 dzone article

      你可以使用Command模式来

      1. 解耦调用者和命令接收者

      2. 实现回调机制

      3. 实现撤消和重做功能

      4. 维护命令历史记录

      java.lang.ThreadCommand 模式的一个很好的实现。您可以将 Thread 视为调用者和实现 Runnable 的类作为 ConcreteCommonad/Receiverrun() 方法作为 Command

      命令模式的撤消/重做版本可以在 Theodore Norvell'sarticle

      阅读

      策略:

      策略模式很容易理解。当

      一个算法有多个实现,算法的实现可以根据特定条件在运行时改变

      航空公司预订系统的票价组件为例

      航空公司希望在不同的时间段(高峰期和非高峰期)提供不同的票价。在非高峰旅行期间,它希望通过提供有吸引力的折扣来刺激需求。

      策略模式的关键要点:

      1. 这是一种行为模式
      2. 它基于委托
      3. 它通过修改方法行为来改变对象的内部结构
      4. 用于在算法族之间切换
      5. 它在运行时改变对象的行为

      带有代码示例的相关帖子:

      Using Command Design pattern

      Real World Example of the Strategy Pattern

      【讨论】:

        【解决方案7】:

        对我来说,区别在于意图之一。这两种模式的实现非常相似,但用途不同:

        • 对于策略,使用对象的组件知道 对象做了什么(并将使用它来执行自己的部分工作),但它并不关心 如何做到这一点。

        • 对于命令,使用对象的组件既不知道命令做什么,也不知道如何执行它——它只知道如何调用它。调用者的任务只是运行命令——命令执行的处理不构成调用者核心工作的一部分。

        这就是区别——使用组件的对象是否真的知道或关心组件的功能?大多数情况下,这可以根据模式对象是否向其调用者返回值来确定。如果调用者关心模式对象的作用,那么它可能希望它返回一些东西,这将是一个策略。如果它不关心任何返回值,它可能是一个命令(注意,像 Java Callable 之类的东西仍然是一个命令,因为尽管它返回一个值,但调用者并不关心该值 - 它只是将它传回到最初提供命令的任何东西)。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-05-31
          • 2019-03-03
          • 1970-01-01
          • 1970-01-01
          • 2020-11-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多