Controlling Access by Making Methods Private

Problem

You've refactored your code (or written it for the first time) and ended up a method that should be marked for internal use only. You want to prevent outside objects from calling such methods.

Solution

Use private as a statement before a method definition, and the method will not be callable from outside the class that defined it. This class defines an initializer, a public method, and a private method:

class SecretNumber def initialize @secret = rand(20) end def hint puts "The number is #{"not " if secret <= 10}greater than 10." end private def secret @secret end end s = SecretNumber.new s.secret # NoMethodError: private method 'secret' called for # # s.hint # The number is greater than 10.

Unlike in many other programming languages, a private method in Ruby is accessible to subclasses of the class that defines it:

class LessSecretNumber < SecretNumber def hint lower = secret-rand(10)-1 upper = secret+rand(10)+1 "The number is somewhere between #{lower} and #{upper}." end end ls = LessSecretNumber.new ls.hint # => "The number is somewhere between -3 and 16." ls.hint # => "The number is somewhere between -1 and 15." ls.hint # => "The number is somewhere between -2 and 16."

 

Discussion

Like many parts of Ruby that look like special language features, Ruby's privacy keywords are actually methods. In this case, they're methods of Module. When you call private, protected, or public, the current module (remember that a class is just a special kind of module) changes the rules it applies to newly defined methods from that point on.

Most languages that support method privacy make you put a keyword before every method saying whether it's public, private, or protected. In Ruby, the special privacy methods act as toggles. When you call the private keyword, all methods you define after that point are declared as private, until the module definition ends or you call a different privacy method. This makes it easy to group methods of the same privacy levela good, general programming practice:

class MyClass def public_method1 end def public_method2 end protected def protected_method1 end private def private_method1 end def private_method2 end end

Private and protected methods work a little differently in Ruby than in most other programming languages. Suppose you have a class called Foo and a subclass SubFoo. In languages like Java, SubFoo has no access to any private methods defined by Foo. As seen in the Solution, Ruby provides no way to hide a class's methods from its subclasses. In this way, Ruby's private works like Java's protected.

Suppose further that you have two instances of the Foo class, A and B. In languages like Java, A and B can call each other's private methods. In Ruby, you need to use a protected method for that. This is the main difference between private and protected methods in Ruby.

In the example below, I try to add another type of hint to the LessSecretNumber class, one that lets you compare the relative magnitudes of two secret numbers. It doesn't work because one LessSecretNumber can't call the private methods of another LessSecretNumber:

class LessSecretNumber def compare(other) if secret == other.secret comparison = "equal to" else comparison = secret > other.secret ? "greater than" : "less than" end "This secret number is #{comparison} the secret number you passed in." end end a = LessSecretNumber.new b = LessSecretNumber.new a.hint # => "The number is somewhere between 17 and 22." b.hint # => "The number is somewhere between 0 and 12." a.compare(b) # NoMethodError: private method 'secret' called for # #

But if I make make the secret method protected instead of private, the compare method starts working. You can change the privacy of a method after the fact by passing its symbol into one of the privacy methods:

class SecretNumber protected :secret end a.compare(b) # => "This secret number is greater than the secret number you passed in." b.compare(a) # => "This secret number is less than the secret number you passed in."

Instance variables are always private: accessible by subclasses, but not from other objects, even other objects of the same class. If you want to make an instance variable accessible to the outside, you should define a getter method with the same name as the variable. This method can be either protected or public.

You can trick a class into calling a private method from outside by passing the method's symbol into Object#send (in Ruby 1.8) or Object#funcall (in Ruby 1.9). You'd better have a really good reason for doing this.

s.send(:secret) # => 19

 

See Also

Категории