4

上下文:我正在尝试在 Ruby 中建立一个装饰器模式。由于装饰器应该将所有未知方法委托给底层对象,因此我使用了 Delegator 类。我本可以使用 SimpleDelegator,但我想完全了解我在做什么。

所以我提出的基本代码是:

class Decorator < Delegator
  def initialize(component)
    super
    @component = component
  end

  def __setobj__(o); @component = o   end
  def __getobj__;    @component       end
  def send(s, *a);   __send__(s, *a)  end
end

这与 SimpleDelegator 的实现完全相同。看起来不错。

但我不想让处理装饰器的代码知道它正在操纵装饰器。我想要完全透明。

这一刻Decorator.new(Object.new).class回来了Decorator

所以我修改了一下,想出了这个:

class Decorator < Delegator
  undef_method :==
  undef_method :class
  undef_method :instance_of?

  # Stores the decorated object
  def initialize(component)
    super
    @component = component
  end

  def __setobj__(o); @component = o   end
  def __getobj__;    @component       end
  def send(s, *a);   __send__(s, *a)  end
end

这样,我可以安全地使用classinstance_of?在我的 Decorated 对象上,它会通过 method_missing(由 Delegator 实现)将方法发送到底层对象。

问题是:我不明白为什么我必须 undef:class:instance_of?. 我可以看到 BasicObject 定义了:==所以我不得不取消定义它但是那两个呢?我查看了 BasicObject 文档和 C 代码中的一些内容,但没有找到任何东西。我查看了 Delegator 文档和代码,也没有找到任何东西。似乎 Delegator 包含 Kernel 模块,但是 Kernel#class 还是 Kernel#instance_of?不存在。

这两种方法是从哪里来的?如果它们根本没有实施,为什么我需要取消定义它们?我想我一定是遗漏了一些关于 Ruby 的对象模型之类的东西。

谢谢。

4

1 回答 1

3

您可以通过检查方法获得提示:

Decorator.instance_method(:class)
  # =>  #<UnboundMethod: Decorator(#<Module:0x00000102137498>)#class> 

该方法的所有者Decorator实际上是在#<Module:0x00000102137498>. 所以有一个匿名模块来定义它。有趣...让我们看看:

Decorator.ancestors
  # => [Decorator, Delegator, #<Module:0x00000102137498>, BasicObject] 

又是那个模块,在Delegator和之间BasicObject。所以Delegator不直接派生自BasicObject. 如果您查看源代码,lib/delegate.rb您会发现:

class Delegator < BasicObject
  kernel = ::Kernel.dup
  kernel.class_eval do
    [:to_s,:inspect,:=~,:!~,:===,:<=>,:eql?,:hash].each do |m|
      undef_method m
    end
  end
  include kernel
  # ...

所以制作了一个模块的副本Kernel,它没有to_s,inspect等......但仍然有classand instance_of?。它包含在Delegator其中,这就是它们的来源。

请注意,Object通过包含模块来继承相同的方法Kernel(但它当然包含完整的模块):

42.method(:class) # => #<Method: Fixnum(Kernel)#class>

这在Object文档中有所说明:

对象混合在内核模块中,使内置内核函数可以全局访问。尽管 Object 的实例方法是由 Kernel 模块定义的,但为了清楚起见,我们选择在此处记录它们。

于 2012-07-10T03:44:55.797 回答