Understanding Ruby class vs instance methods

2020-07-24 06:41发布

问题:

I have the following code:

#!/usr/bin/ruby

class Person
  def self.speak
    p = self.new
    puts "Hello"
    p.chatter
  end

private

  def chatter
    puts "Chattering"
  end
end

p = Person.new
Person.speak

I'd like to make chatter private, accessible only within p.. but I want p to be able to access it within the class method. Is there a better way to design this so chatter isn't available to the public, but a "factory" method like self.speak can call chatter?

回答1:

In Ruby 1.8.7, "send" bypasses the usual protections against calling private methods:

#!/usr/bin/ruby1.8

class Person

  def self.speak
    puts "Hello"
    new.send(:chatter)
  end

  def speak
    puts "Hello"
    puts chatter
  end

  private

  def chatter
    puts "Chattering"
  end

end

Person.speak        # => Hello
                    # => Chattering
Person.new.speak    # => Hello
                    # => Chattering

However, what you want can be achieved without any voodoo, by simply having the class method do all the work, and the instance method defer to the class method:

class Person

  def self.speak
    puts "Hello"
    puts "Chatter"
  end

  def speak
    self.class.speak
  end

end

If you had more than a few of these forwarding methods, it might be convenient to make a helper method that makes them for you:

module DelegateToClass

  def delegate_to_class(name)
    define_method(name) do |*args|
      self.class.send(name, *args)
    end
  end

end

class Person

  extend DelegateToClass

  def self.speak
    puts "Hello"
    puts "Chatter"
  end
  delegate_to_class :speak

end

The built-in module Forwardable can do this just as well:

require 'forwardable'

class Person

  extend Forwardable

  def self.speak
    puts "Hello"
    puts "Chatter"
  end

  def_delegator self, :speak

end

def_delegator also bypasses protections against private methods.



回答2:

There's a number of ways to do this. One approach would be a class method which instantiates a new instance and calls the #speak method on the instance.

class Person
  def self.speak
    new.speak
  end

  def speak
    puts "Hello"
    chatter
  end

  private

  def chatter
    puts "Chattering"
  end
end

Then you could call it in either the class or instance context:

p = Person.new
p.speak
# ...or...
Person.speak


标签: ruby