从Ruby中的实例方法调用受保护的类方法

Posted

技术标签:

【中文标题】从Ruby中的实例方法调用受保护的类方法【英文标题】:Calling protected class method from instance method in Ruby 【发布时间】:2012-06-23 21:18:48 【问题描述】:

我一直有这个令人烦恼的反复出现的主题;比方说,我有一个类,它定义了一个实例方法和一个受保护的类方法。实例方法必须调用类方法。为了做到这一点,我不得不打破可见性规则并使用危险的“发送”功能。像这样的:

class Bang
    def instance_bang
      self.class.send(:class_band)
    end

    protected
    def self.class_bang
      puts "bang"
    end
end

我觉得这很糟糕,因为类方法应该在类范围内使用,因此应该在其中保持可见和可调用,对吧?是否有另一种方法可以在实例方法中使用类方法,需要依赖“发送”函数,因此不会破坏可见性?

更新:

根据 Sergio Tulentsev 的回复(感谢纠正),我将使用代码 sn-p 更新我的担忧,该代码总结了我对仍在定义范围内时考虑方法可见性的担忧。

class Bang
  def instance_bang
    private_bang = 1
    self.private_bang(private_bang)
  end
  private
  def private_bang(p)
    puts "bang"
    p
  end
end

调用 Bang.new.instance_bang 将引发异常,除非您在该 private_bang 调用上使用 send(这次我检查了它:)。

【问题讨论】:

这实际上是两个问题,可能应该分开。第一个是关于调用受保护的类方法,但您的代码示例无效,因为调用 protected 仅影响实例方法(尝试 class @ChuckE 如果你 %s/class_band/class_bang/g,这似乎可以工作,至少对我来说是这样。 【参考方案1】:

编辑:回答更新后的问题

禁止使用显式接收者调用私有方法。您要么必须使用隐式接收器(private_bang,没有self),要么使用send。请参阅my another answer 了解更多信息。

顺便说一句,最初的问题是关于从实例方法调用类实例方法。您的澄清不包括这一点。但如果仍然如此,您必须使用self.class.send 或公开该方法(以便您可以使用显式接收器)。

【讨论】:

我很高兴你在 dangerous 周围加上引号。 关于详细部分的协议。事实上,对于它起作用的类方法,mea culpa,应该在发布之前对其进行测试。我将在后面的评论中解释我的主要关注点。 这个解决方案中的protected 似乎没有做任何事情。 class_bang 方法仍然是公开的,可以通过 Bang.public_methodsBang.protected_methods 看到。 请从代码示例中删除“受保护”。它没有效果并且具有误导性。【参考方案2】:

让我们考虑一个私有类方法(因为受保护的类方法don't make sense)。

我们知道实例可以调用自己的私有方法,只要它不使用显式接收器 (self.call_something_private)。 您似乎还希望实例可以在其自己的类上调用私有类方法,但事实并非如此。

让我们看看不使用send 的方法。

privateprotected 宏只影响当前作用域的实例方法,而不影响类方法。以下是重写原始代码的三种方法:

class Bang
  def instance_bang
    self.class.class_bang
  end

  # declare method visibility after
  def self.class_bang
    puts "bang"
  end
  private_class_method :class_bang

  # inline
  private_class_method def self.class_bang
    puts "bang"
  end

  # class scope
  class << self
    # the private macro works here because we're inside the class scope
    private

    def class_bang
      puts "bang"
    end
  end
end

所以现在我们要在类上公开一个接口来调用class_bang,但前提是它被Bang 的实例调用。

class Bang
  def instance_bang
    self.class.invoke_class_bang(self)
  end

  class << self
    private

    def class_bang
      puts "bang"
    end

    public

    # we ask the receiver to pass itself as an argument ...
    def invoke_class_bang(receiver)
      # ... so that we can check whether it's
      class_bang if receiver.is_a?(Bang)
    end
  end
end

虽然这不是一个很好看的解决方案。这是一种更隐秘的方式:

class Bang
  def initialize
    def self.instance_bang() self.class.method(:class_bang).call end
  end

  class << self
    private
    def class_bang
      puts "bang"
    end
  end
end

【讨论】:

【参考方案3】:

“类方法应该在类范围内使用,因此应该在其中保持可见和可调用,对吗?”是的,这是正确的,这就是 Ruby 表现出来的行为。 (澄清一点,实例范围不在“类范围内”。它们适当地是分开的。)

send的解决方案是子类化或重新打开类添加公共类方法来访问受保护的类方法。

【讨论】:

以上是关于从Ruby中的实例方法调用受保护的类方法的主要内容,如果未能解决你的问题,请参考以下文章

从不同包的另一个实例的子类调用受保护的方法

如何从 Derived 内部调用 Base 实例中的受保护成员函数?

从 PHP 中的类外部调用受保护的方法

澄清 Ruby 中“私有”和“受保护”的定义?

受保护的静态方法访问

有没有办法从 Ruby 中的实例调用私有类方法?