【问题标题】:Command Pattern in RubyRuby 中的命令模式
【发布时间】:2017-09-18 00:37:35
【问题描述】:

我喜欢 Ruby,并且正在学习设计模式。这是命令模式的示例。领域模型如下:

  • Command - 所有命令的通用接口
  • OnCommandOffCommand - 实现 Command 接口的特定命令,都使用 TV 对象作为接收器
  • 按钮 - 使用 Command 对象获取结果
  • RemoteContol - 客户端,初始化 Button 和 Command 对象 Button - 使用 Command 对象执行命令
  • 电视 - 接收命令

这里是这个问题的一些代码:

class TV
  def on
    puts 'TV is on'
  end

  def off
    puts 'TV is off'
  end
end

class Command
  class Error < StandardError; end

  def execute
    raise Error
  end
end

class OnCommand < Command
  attr_reader :tv

  def initialize(tv)
    @tv = tv
  end

  def execute
    tv.on
  end
end

class OffCommand < Command
  attr_reader :tv

  def initialize(tv)
    @tv = tv
  end

  def execute
    tv.off
  end
end

class Button
  attr_reader :command

  def initialize(command)
    @command = command
  end

  def execute_command
    command.execute
  end
end

class RemoteControl
  attr_reader :tv

  def initialize(tv)
    @tv = tv
    @buttons = {
      on: Button.new(OnCommand.new(tv)),
      off: Button.new(OffCommand.new(tv))
    }
  end

  def use_button(name)
    button(name).execute_command
  end

  private

  def button(name)
    @buttons.fetch name
  end
end

这是用法示例:

tv = TV.new                                                                                                             
remote_control = RemoteControl.new(tv)                                                                                  
remote_control.use_button(:on) # TV is on                                                                                         
remote_control.use_button(:off) # TV is off 

我对命令模式的理解程度如何?这是一个很好的例子吗?

感谢任何想法

更新

我想将 TV 替换为 InfraredTransmitter 作为命令接收器将使示例更合适。因为只有电视本身才能执行“on”命令。 RemoteControl 可以物理访问其红外发射器,该发射器能够向电视发送不同的命令。

【问题讨论】:

    标签: ruby design-patterns solid-principles command-pattern


    【解决方案1】:

    Command 不应有对TV 的任何引用。这里不是命令,它是tv-only命令。这是更红宝石的方法:

    class Command
      def call(*args, &cb)
        target, command, *rest = *args
        target.public_send command, *rest, &cb
      end
    end
    
    %i|on off|.each do |command|
      const_set("Command#{command.to_s.capitalize}", Class.new(Command) do
        def call(*args, &cb)
          target, *rest = *args
          super(target, command, *rest, &cb)
        end
      end
    end
    
    class Button
      attr_reader :command
    
      def initialize(command) # accepts :on, "on", CommandOn
        @command = case command
                   when Class then command
                   when Symbol, String
                     const_get("Command#{command.to_s.capitalize}")
                   end.new
      end
    
      def call(target, *args, &cb)
        command.(target, *args, &cb)
      end
    end
    
    class RemoteControl
      def initialize(tv)
        @tv = tv
        @buttons = %i|on off|.map { |c| [c, Button.new(c)] }.to_h
      end
      def call(command)
        @buttons[command].(@tv)
      end
    end
    
    remote_control = RemoteControl.new(TV.new)
    remote_control.(:on)
    remote_control.(:off)
    

    通过这种方法,Command 可以被重复用于任何其他目标,而无需声明新的Command。它必须与目标无关,否则它不是通用的Command 模式。


    正如@Aetherus 所指出的,在这种方法中,这里根本不需要超类。上面的Command 可能会被安全删除,除非你有一些通用的逻辑要放在那里。

    【讨论】:

    • 如果一个命令只有一种方法,我想我可以用procs替换命令实例。命令适用于一套相互关联的算法,例如做某事和撤消它。
    • @Aetherus Command 模式旨在具有单一方法。是的,在 ruby​​ 中,可以安全地使用 procs,而且,基本上,我确实使用 procs,用方法语法编写。
    • @user3309314 不。例如,在常量中。
    • 是的,这是可能的。但是这样你就不能实现未执行的功能以防万一。
    • @user3309314 您可以使用每个包含 2 个 proc 的哈希来实现撤消。
    【解决方案2】:

    您不需要超类Command。只需定义那些具体的命令。这就是动态编程语言做事的方式。他们不关心类型。他们只关心对象是否具有所需的方法。

    【讨论】:

    • 对于编程语言来说,动态化并不意味着“允许代码重复,放弃 DRY 原则”。如果按上述方式实现(这是错误的,请参阅我的答案以了解详细信息)@tv 处理应移至超类,因此应保留。
    【解决方案3】:

    我看是对的。
    Command 模式有以下参与者:

    Command – 声明用于执行操作的接口
    ConcreteCommand – 定义 Receiver 对象和操作之间的绑定
    Client – 创建一个 ConcreteCommand 对象并设置其接收者
    Invoker – 要求命令执行请求
    Receiver – 知道如何执行相关操作执行请求

    因此,在您的示例中,CommandCommandOnCommandOffCommandConcreteCommand( s)TVReceiverButtonInvokerRemoteControl > 是客户

    【讨论】:

    • 在 OP 中 Command 声明不是用于执行操作的接口,它声明了一个特定于电视的操作,所以,不,这不是一个正确的实现。
    • 我同意@mudasobwa。这不是现实生活中非常幸运的例子,因为只有电视本身才能执行自己的“开”命令。也许用一些 InfraredSensor 替换 TV 作为接收器是更好的例子
    • @mudasobwa,你是对的命令应该被声明为接口(命令 - 声明一个用于执行操作的接口),但总的来说,从命令模式参与者的角度来看,示例对我来说看起来不错。跨度>
    猜你喜欢
    • 2012-10-24
    • 1970-01-01
    • 2010-12-02
    • 1970-01-01
    • 2015-01-13
    • 2018-08-07
    • 2017-12-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多