在执行时间内向类添加方法的最佳方法

时间:2012-03-23 12:41:40

标签: ruby metaprogramming

我必须在执行时添加方法。

class ExtendableClass
end

要添加的方法在独立的类中声明。

module ExtensionClassOne
  def method_one
  end
end

module ExtensionClassTwo
  def method_two
  end
end

我正在寻找一种(优雅)机制,将所有扩展类方法添加到ExtendableClass中。

方法1

我正在考虑明确包含扩展类,如:

ExtendableClass.send( :include, ExtensionClassOne )
ExtendableClass.send( :include, ExtensionClassTwo )

但每次定义新的扩展类时,看起来有点不得不调用此私有方法。

方法2

所以我正在寻找一种自动方式将include这些方法导入我的ExtendableClass课程。

我在考虑为此扩展类声明一个特定的祖先:

class ExtensionClassOne < Extension
  def method_one
  end
end

然后我需要一种机制来了解一个类的所有孩子 ......类似于ancestors的对话。

一旦我有了这个列表,我就可以轻松地ExtendableClass.include所有类列表。即使我必须在这里调用私有方法。

方法3

当此类用作祖先时,还继承Extension类,检测声明时间。以ActiveSupport.included method的工作方式,如事件绑定。然后在那里制作include

实施方法2 方法3 的任何解决方案?你推荐接近1 吗?新方法?

6 个答案:

答案 0 :(得分:1)

included方法实际上是一个钩子。无论何时继承自:

,都会调用它
module Extensions
  def someFunctionality()
    puts "Doing work..."
  end
end
class Foo
  def self.inherited(klass)
    klass.send(:include, Extensions) #Replace self with a different module if you want
  end
end
class Bar < Foo
end
Bar.new.someFunctionality                  #=> "Doing work..."

还有included钩子,当你被包括在内时调用它:

module Baz
  def self.included(klass)
    puts "Baz was included into #{klass}"
  end
end
class Bork
  include Baz
end

输出:

Baz was included into Bork

答案 1 :(得分:1)

方法4是在Object

中的类级别定义一个宏
class Object
  def self.enable_extension
    include InstanceExtension
    extend ClassExtension
  end
end

并在您想要扩展的所有课程中调用此宏。

class Bacon
  enable_extension
end

Car.enable_extension

这样,

  • 您不必使用#send来规避封装(方法1)
  • 你可以继承任何你想要的类,因为一切都从Object继承(除了1.9的BasicObject)
  • 您的扩展程序的使用是声明性的,而不是隐藏在某些挂钩

下行:你monkeypatch内置类可能打破世界。选择长名称和描述性名称。

答案 2 :(得分:1)

编辑:鉴于您对我对该问题的评论的回答,我认为这不是您想要的。在这种情况下,我认为你的“方法1”没有问题;这就是我要做的。或者,不要使用send绕过私有方法,只需重新打开类:

class ExtendableClass
  include ExtensionOne
end

假设我明白你想要什么,我会这样做:

module DelayedExtension
  def later_include( *modules )
    (@later_include||=[]).concat( modules )
  end
  def later_extend( *modules )
    (@later_extend||=[]).concat( modules )
  end
  def realize_extensions # better name needed
    include *@later_include unless !@later_include || @later_include.empty?
    extend  *@later_extend  unless !@later_extend  || @later_extend.empty?
  end
end

module ExtensionOne
end

module ExtensionTwo
  def self.included(klass)
    klass.extend ClassMethods
  end
  module ClassMethods
    def class_can_do_it!; end
  end
end

class ExtendableClass
  extend DelayedExtension
  later_include ExtensionOne, ExtensionTwo
end

original_methods = ExtendableClass.methods
p ExtendableClass.ancestors
#=> [ExtendableClass, Object, Kernel, BasicObject]

ExtendableClass.realize_extensions

p ExtendableClass.ancestors
#=> [ExtendableClass, ExtensionOne, ExtensionTwo, Object, Kernel, BasicObject]

p ExtendableClass.methods - original_methods
#=> [:class_can_do_it!]

答案 3 :(得分:1)

@fguillen,你是对的,“明确的方式是最干净的方法”。既然如此,为什么不使用可以想象的最“明确”的代码:

class Extendable
end

class Extendable
  def method_one
    puts "method one"
  end
end

class Extendable
  def method_two
    puts "method two"
  end
end

...换句话说,如果你定义的模块一旦被定义就会自动包含在一个类中,为什么还要为模块烦恼呢?只需将“扩展”方法直接添加到课程中即可!

答案 4 :(得分:0)

在你做了一个非常有趣的命题后,我意识到显式方式是最干净的方法。如果我们从你的答案中添加一些建议,我想我会选择这个:

# extendable.rb
class Extendable
  def self.plug( _module )
    include( _module )
  end
end

# extensions/extension_one.rb
module ExtensionOne
  def method_one
    puts "method one"
  end
end
Extendable.plug( ExtensionOne )

# extensions/extension_two.rb
module ExtensionTwo
  def method_two
    puts "method two"
  end
end
Extendable.plug( ExtensionTwo )

# result
Extendable.new.method_one # => "method one"
Extendable.new.method_two # => "method two"

答案 5 :(得分:0)

一个非常棘手的解决方案,我认为过度工程化太多,将采用@Linux_iOS.rb.cpp.c.lisp.m.sh评论过的继承的钩子并保留所有和Set中的每个子类,并将其与 method_missing 的@Mikey Hogarth命题相结合,以便每次调用{中的方法时查找所有这些子类方法{1}}课程。像这样:

Extendable

但是# code simplified and no tested # extendable.rb class Extendable @@delegators = [] def self.inherited( klass ) @@delegators << klass end def self.method_missing # ... searching in all @@delegators methods end end # extensions/extension_one.rb class ExtensionOne < Extendable def method_one end end (和method_missing)的逻辑会变得非常复杂和肮脏。

我不喜欢这个解决方案,只是让它在这里研究它的可能性。