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

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

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

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

    class Worker
      case App::OPTION
      when "dry-run"
        include FileUtils::DryRun
        etc.
    
  2. 创建子类,唯一的区别是它们包含哪个版本的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,但我希望有一些我没有想到的更直接的方法。

【问题讨论】:

    标签: ruby include module


    【解决方案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 中,可以说这并不是真正的“隐私”,但有些方法被标记为私有并非没有原因。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-10-28
        • 1970-01-01
        • 2020-04-08
        • 1970-01-01
        • 1970-01-01
        • 2011-09-16
        • 2011-12-29
        • 1970-01-01
        相关资源
        最近更新 更多