我正在尝试通过构建一个基本的Campfire机器人来学习ruby,以便在工作中使用。我已经相当远了(它有效!)并且学到了很多东西(它有效!)但是现在我试图通过将要执行的动作分离到它们自己的类中来使它更复杂一些,这样它们就可以了在破碎时可以更容易地编写/修复。如果你有兴趣看到所有(可能是糟糕的)代码,那就是up on GitHub。但是为了这个问题,我会缩小范围。
理想情况下,我希望能够轻松地创建插件,将它们命名为类名,然后将它们放入项目根目录中的“actions”目录中,在运行时将它们实例化。我希望插件本身尽可能简单地编写,所以我希望它们都能从action
类继承一些基本的方法和属性。
目前存在action.rb
:
module CampfireBot
class Action
@handlers = {}
def initialize(room)
@room = room
end
class << self
attr_reader :handlers
attr_reader :room
def hear(pattern, &action)
Action.handlers[pattern] = action
end
end
end
end
@room
是房间对象,@handlers
是模式和块的哈希。我有点不明白为什么我必须进行class << self
调用,但这是我可以让子插件类看到hear
方法的唯一方法。
然后我尝试创建一个像这样的简单插件(名为Foo.rb
):
class Foo < CampfireBot::Action
hear /foo/i do
@room.speak "bar"
end
end
然后我将我的插件在bot.rb中实例化,如下所示:
def load_handlers(room)
actions = Dir.entries("#{BOT_ROOT}/actions").delete_if {|action| /^\./.match(action)}
action_classes = []
# load the source
actions.each do |action|
load "#{BOT_ROOT}/actions/#{action}"
action_classes.push(action.chomp(".rb"))
end
# and instantiate
action_classes.each do |action_class|
Kernel.const_get(action_class).new(room)
end
@handlers = Action.handlers
end
当模式与以下匹配时,在room.rb
内调用块:
handlers.each do |pattern, action|
if pattern.match(msg)
action.call($~)
end
end
如果我在puts @room
的初始化中Action
,我会在控制台中看到房间对象打印出来。如果我在puts "foo"
的{{1}}方法中Foo.rb
,我会在控制台上打印出hear
(因此,模式匹配正在运行)。但是,我无法从父类中读取foo
对象(它作为一个nil对象出现)。很显然,我错过了一些关于它应该如何工作的东西。
此外,如果我做了一些事情来使插件更清晰(对于更大的功能)并重写它:
@room
我得到class Foo < CampfireBot::Action
hear /foo/i do
say_bar
end
def say_bar
@room.speak "bar"
end
end
。
答案 0 :(得分:1)
hear
的定义可以从class << self
块中取出并更改为:
def self.hear(pattern, &action)
Action.handlers[pattern] = action
end
得到完全相同的结果。这也立即解释了这个问题。 hear
是一种类方法。 say_bar
是一种实例方法。您无法从类方法中调用实例方法,因为根本没有可用的类实例。
要了解class << self
位,您必须自己进行阅读和实验:我不会尝试改进已经说过的内容。我只会说在class << self .. end
块中,self
指的是CampfireBot::Action
类的本征类或元类。这是包含CampfireBot :: Action类定义的Class
类的实例。