如何选择要在 Ruby 中动态包含的模块版本?
Posted
技术标签:
【中文标题】如何选择要在 Ruby 中动态包含的模块版本?【英文标题】:How can I choose which version of a module to include dynamically in Ruby? 【发布时间】:2011-03-22 11:27:53 【问题描述】:我正在编写一个小型 Ruby 命令行应用程序,它使用标准库中的 fileutils
进行文件操作。根据用户调用应用程序的方式,我希望包含FileUtils
、FileUtils::DryRun
或FileUtils::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
类,因此所有Worker
s 都将使用相同的设置。现在它实际上使用元类并允许 pre-Worker 设置。
你也可以使用(class <<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 中动态包含的模块版本?的主要内容,如果未能解决你的问题,请参考以下文章