这实际上只是在模仿 Brian Campbell 的解决方案。如果你喜欢这个,请也支持他的答案:他做了所有的工作。
#!/usr/bin/env ruby
class Object; def eigenclass; class << self; self end end end
module LogFileReader
class LogFileReaderNotFoundError < NameError; end
class << self
def create type
(self[type] ||= const_get("#{type.to_s.capitalize}LogFileReader")).new
rescue NameError => e
raise LogFileReaderNotFoundError, "Bad log file type: #{type}" if e.class == NameError && e.message =~ /[^: ]LogFileReader/
raise
end
def []=(type, klass)
@readers ||= {type => klass}
def []=(type, klass)
@readers[type] = klass
end
klass
end
def [](type)
@readers ||= {}
def [](type)
@readers[type]
end
nil
end
def included klass
self[klass.name[/[[:upper:]][[:lower:]]*/].downcase.to_sym] = klass if klass.is_a? Class
end
end
end
def LogFileReader type
在这里,我们创建了一个名为LogFileReader 的全局方法(实际上更像是一个过程),它与我们的模块LogFileReader 同名。这在 Ruby 中是合法的。歧义是这样解决的:该模块将始终是首选,除非它显然是一个方法调用,即您要么将括号放在末尾 (Foo()) 要么传递一个参数 (Foo :bar)。
这是一个在 stdlib 中的一些地方使用的技巧,也用于 Camping 和其他框架。因为像 include 或 extend 这样的东西实际上不是关键字,而是接受普通参数的普通方法,你不必将实际的 Module 作为参数传递给它们,你也可以传递任何 评估为Module。事实上,这甚至适用于继承,写class Foo < some_method_that_returns_a_class(:some, :params)是完全合法的。
使用这个技巧,您可以让它看起来像是继承自一个泛型类,即使 Ruby 没有泛型。例如,它在委托库中使用,您可以在其中执行class MyFoo < SimpleDelegator(Foo) 之类的操作,然后发生的情况是SimpleDelegator 方法 动态创建并返回SimpleDelegator 的匿名子类class,它将所有方法调用委托给Foo 类的实例。
我们在这里使用了类似的技巧:我们将动态创建一个Module,当它混合到一个类中时,将自动将该类注册到LogFileReader 注册表中。
LogFileReader.const_set type.to_s.capitalize, Module.new {
在这条线上有很多事情要做。让我们从右边开始:Module.new 创建一个新的匿名模块。传递给它的块成为模块的主体——它与使用 module 关键字基本相同。
现在,转到const_set。这是一种设置常数的方法。所以,就像说FOO = :bar,except一样,我们可以将常量的名称作为参数传入,而不必事先知道。由于我们在 LogFileReader 模块上调用该方法,因此常量将在该命名空间内定义,IOW 将命名为 LogFileReader::Something。
那么,是这个常量的名字是什么?好吧,这是传递给方法的type 参数,大写。因此,当我传入:cvs 时,生成的常量将是LogFileParser::Cvs。
我们将常量设置为什么?致我们新创建的匿名模块,它现在不再是匿名的!
所有这些实际上只是一种冗长的说法module LogFileReader::Cvs,除了我们事先不知道“Cvs”部分,因此不可能这样写。
eigenclass.send :define_method, :included do |klass|
这是我们模块的主体。在这里,我们使用define_method 来动态定义一个名为included 的方法。而且我们实际上并没有在模块本身上定义方法,而是在模块的eigenclass上(通过我们上面定义的一个小辅助方法),这意味着该方法不会成为实例方法,而是一种“静态”方法(在 Java/.NET 术语中)。
included 实际上是一个特殊的钩子方法,它被 Ruby 运行时调用,每次一个模块被包含到一个类中,并且该类作为参数传入。所以,我们新创建的模块现在有一个钩子方法,当它被包含在某个地方时会通知它。
LogFileReader[type] = klass
这就是我们的钩子方法所做的:它将传递给钩子方法的类注册到LogFileReader注册表中。它注册它的关键是上面LogFileReader方法中的type参数,由于闭包的魔力,它实际上可以在included方法中访问。
end
include LogFileReader
最后但同样重要的是,我们将LogFileReader 模块包含在匿名模块中。 [注意:我在原始示例中忘记了这一行。]
}
end
class GitLogFileReader
def display
puts "I'm a git log file reader!"
end
end
class BzrFrobnicator
include LogFileReader
def display
puts "A bzr log file reader..."
end
end
LogFileReader.create(:git).display
LogFileReader.create(:bzr).display
class NameThatDoesntFitThePattern
include LogFileReader(:darcs)
def display
puts "Darcs reader, lazily evaluating your pure functions."
end
end
LogFileReader.create(:darcs).display
puts 'Here you can see, how the LogFileReader::Darcs module ended up in the inheritance chain:'
p LogFileReader.create(:darcs).class.ancestors
puts 'Here you can see, how all the lookups ended up getting cached in the registry:'
p LogFileReader.send :instance_variable_get, :@readers
puts 'And this is what happens, when you try instantiating a non-existent reader:'
LogFileReader.create(:gobbledigook)
这个新的扩展版本允许定义LogFileReaders 的三种不同方式:
- 将自动找到名称与模式
<Name>LogFileReader 匹配的所有类,并为:name 注册为LogFileReader(参见:GitLogFileReader),
- 在
LogFileReader 模块中混合并且名称与<Name>Whatever 模式匹配的所有类都将注册为:name 处理程序(请参阅:BzrFrobnicator)和
- 在
LogFileReader(:name) 模块中混合的所有类都将为:name 处理程序注册,无论其名称如何(请参阅:NameThatDoesntFitThePattern)。
请注意,这只是一个非常人为的演示。例如,它绝对是非线程安全的。它也可能泄漏内存。谨慎使用!