accessing protected methods in Ruby

2019-06-17 03:20发布

问题:

I am trying to work our for myself access modifiers in Ruby. I have:

class Person
  def initialize (first_name, last_name, age)
        @first_name=first_name
        @last_name=last_name
        @age=age
    end


    def show()
        puts @first_name
        puts @last_name
        puts @age
    end

protected
  def compare(other)
    self.instance_variable_get(:@age)<=>other.instance_variable_get(:@age)
  end

end

p1=Person.new("Some", "Body", "99")
p1.show
puts "\n"

p2=Person.new("Who", "Ever", "21")
p2.show
puts "\n"

p1.compare(p2)

I am getting the error "protected method `compare' called for # (NoMethodError)" I have tried calling from within the class and without. I pasted the without version here. I thought that protected methods could be called on other objects of the same class. What does this error mean and how would I properly use a protected method here? Thank you for your help.

回答1:

You got the wrong view of the protected visibility. The Ruby doc says:

The second visibility is protected. When calling a protected method the sender must be a subclass of the receiver or the receiver must be a subclass of the sender. Otherwise a NoMethodError will be raised.

So the restriction of the visibility is applied to the sender, not the receiver as what you thought.

If you want to call compare outside of the instance methods, you need to use public visibility. You need to remove the protected modifier if you can. This is the recommended way.

If the code is fixed and you cannot modify that piece of code, you can use the Object#send method. Object#send will bypass the visibility constraint and can access even private methods.

p1.send(:compare, p2)

Or you can reopen the class and change the visibility of the compare class:

# you code here

# reopen and modify visibility
class Person
  public :compare
end

p1.compare(p2)


回答2:

You can call a protected method in a public method of the class...

class Person
  def initialize (first_name, last_name, age)
    @first_name=first_name
    @last_name=last_name
    @age=age
  end

  def same_age?(other)
    age == other.age
  end

  def show
    puts @first_name
    puts @last_name
    puts @age
  end

  protected

  def age
    @age
  end

end

p1=Person.new("Some", "Body", "99")
p1.show
puts "\n"

p2=Person.new("Who", "Ever", "21")
p2.show
puts "\n"

# calls a method that calls a protected method
p1.same_age?(p2)
=> false

# but you can't call #age directly...
begin 
 p1.age
rescue NoMethodError
  puts "no method error (protected)"
end