让外部代码在你的类中融合听起来很危险,并且正如你所猜测的那样会导致名称冲突。它还可能导致意外行为,因为您允许外部代码与您的类内部发生混乱。
处理插件的最佳方法是封装将被替换的行为。让我们举个例子:
假设您正在构建一个 Logger,我们可以想象您可以创建插件来指定将写入日志的位置。
然后,您可以创建以下类:
class Logger
def initialize(store)
@store = store
end
def info(message)
@store.write(:info, message)
end
end
class StandardOutputStore
def write(level, message)
puts "#{level}: #{message}"
end
end
class FileStore
def initalize(filename)
@filename = filename
end
def write(level, message)
File.open(@filename, "a") do |file|
file.write("#{level}: #{message}")
end
end
end
然后你可以通过这种方式实例化正确的插件:
logger = Logger.new(StandardOutputStore.new)
logger.warn "hello"
logger = Logger.new(FileStore.new("/tmp/log"))
logger.warn "hello"
这种做事方式提供了模块化、更大的灵活性,并且比使用外部代码重载类中的内容更可靠。
加载在目录中找到的模块是玩弄 Dir.glob 和 require 的问题。要探索在您的插件目录下找到的内容,您可以强制您的用户在模块中编写插件,例如 Foo::Plugins::FileStore 并在需要文件后检查 Foo::Plugins 模块中存在哪些常量。有关详细信息,请参阅http://www.ruby-doc.org/core-1.9.3/Module.html#method-i-constants。
编辑:由于您向 ERB 模板提供了方法,因此您没有任何要映射的接口,因此您可以执行以下操作:
插件采用以下形式:
module Plugins
module Emoticons
def smile
":-)"
end
end
end
然后,您可以使用以下内容加载它们:
Dir["/plugins/*.rb"].each {|file| require file }
并包含它们,因为 TemplateMethods 是一个包含在您的 ERB 模板上下文中的模块。
Plugins.constants.each do |constant|
mod = Plugins.const_get constant
TemplateMethods.send(:include, mod)
end
使用 send 不是很优雅,我建议在 TemplateMethods 上构建一个封装整个事物的类方法,例如 TemplateMethods.load_plugins_method!这里的爆炸会警告动态模块包含所做的侵入性操作。
那应该做的工作:)