如何选择要在 Ruby 中动态包含的模块版本?

Posted

技术标签:

【中文标题】如何选择要在 Ruby 中动态包含的模块版本?【英文标题】:How can I choose which version of a module to include dynamically in Ruby? 【发布时间】:2011-03-22 11:27:53 【问题描述】:

我正在编写一个小型 Rub​​y 命令行应用程序,它使用标准库中的 fileutils 进行文件操作。根据用户调用应用程序的方式,我希望包含FileUtilsFileUtils::DryRunFileUtils::Verbose

由于include 是私有的,但我不能将选择逻辑放入对象的initialize 方法中。 (这是我的第一个想法,从那时起我可以将有关用户选择的信息作为参数传递给new。)我提出了两个似乎可行的选项,但我对其中任何一个都不满意:

    根据用户的选择在应用的命名空间中设置一个全局变量,然后在类中进行条件包含:

    class Worker
      case App::OPTION
      when "dry-run"
        include FileUtils::DryRun
        etc.
    

    创建子类,唯一的区别是它们包含哪个版本的FileUtils。根据用户的选择选择合适的。

    class Worker
      include FileUtils
      # shared Worker methods go here
    end
    class Worker::DryRun < Worker
      include FileUtils::DryRun
    end
    class Worker::Verbose < Worker
      include FileUtils::Verbose
    end
    

第一种方法似乎 DRY-er,但我希望有一些我没有想到的更直接的方法。

【问题讨论】:

【参考方案1】:

如果它是私人的呢?

class Worker
  def initialize(verbose=false)
    if verbose
      (class <<self; include FileUtils::Verbose; end)
    else
      (class <<self; include FileUtils; end)
    end
    touch "test"
  end
end

这包括 FileUtils::something 尤其是 Worker 的元类 - 不在主要的 Worker 类中。不同的worker可以这样使用不同的FileUtils

【讨论】:

“那又怎样”对我来说听起来不错。 (这正是我没有看到的更直接的事情。)谢谢。 糟糕,我的错误。我之前给出的代码将修改Worker 类,因此所有Workers 都将使用相同的设置。现在它实际上使用元类并允许 pre-Worker 设置。 你也可以使用(class &lt;&lt;self; include FileUtils; end)代替extend FileUtils 感谢编辑。我真的很高兴看到这两种方式,因为在一种情况下,我可能希望 include 在每个实例的基础上对班级和其他人都是全局的。 @Konstantin - 感谢您提供替代语法。【参考方案2】:

通过 send 方法有条件地包含模块对我有用,如下面的测试示例所示:

class Artefact
  include HPALMGenericApi
  # the initializer just sets the server name we will be using ans also the 'transport' method : Rest or OTA (set in the opt parameter)
  def initialize server, opt =   
    # conditionally include the Rest or OTA module
    self.class.send(:include, HPALMApiRest) if (opt.empty? || (opt && opt[:using] opt[:using] == :Rest)) 
    self.class.send(:include, HPALMApiOTA) if (opt && opt[:using] opt[:using] == :OTA)    
    # ... rest of initialization code  
  end
end

【讨论】:

【参考方案3】:

如果您想避免“切换”并注入模块,则

def initialize(injected_module)
    class << self
        include injected_module
    end
end

语法不起作用(injected_module 变量超出范围)。你可以使用 self.class.send 技巧,但每个对象实例的扩展对我来说似乎更合理,不仅因为它更短:

def initialize(injected_module = MyDefaultModule)
    extend injected_module
end

但它也最大限度地减少了副作用 - 类的共享且易于更改的状态,这可能导致大型项目中的意外行为。在 Ruby 中,可以说这并不是真正的“隐私”,但有些方法被标记为私有并非没有原因。

【讨论】:

以上是关于如何选择要在 Ruby 中动态包含的模块版本?的主要内容,如果未能解决你的问题,请参考以下文章

ruby/rails:如何确定是不是包含模块?

ruby Ruby:如何创建一个包含相同方法的类和实例变体的模块

Ruby 动态更新嵌套数组中的值

为啥我们要在 Ruby 的类中放置一个模块?

ruby 如何定义和包含模块

如何在 ruby​​ 中引用子模块的“完整路径”?