DEV Community

loading...

Public vs. Private vs. Protected

Sun-Li Beatteay
Engineer obsessed with distributed systems, cats, and whiskey 🥃. I live stream on Twitch: https://www.twitch.tv/paxosraft. Come join the Quorum!
・8 min read

For anyone learning Object Oriented Programming or Ruby or both, you know that there is a lot to learn. I’m still learning new things everyday. One of the topics that bothered me, and still bothers me, is the difference between public, private and protected instance methods.

As an attempt to better understand them, I’ve done some research on the topic and am writing down what I’ve learned. Hopefully this article will not only clear up the matter in my own head but help anyone else who is confused as well.

A basic understanding of Object Oriented Programming will be needed to understand this article. If you are unfamiliar with OOP, I suggest reading up on it first.

First Things First

What are public, private, and protected instance methods? Why do we use them? When do we use them?

To make it simple: public, private and protected methods are just that. Methods. You use them to perform certain functions on your code. The difference between the three comes from who and/or what has access to them.

Why do we use them? To provide a easy and simple interface for our classes. They allow us to write clean code and provide a enjoyable experience for the consumers of that code while protecting any sensitive data at the same time.

As for when do we use each one, that will require us to go more in-depth.

Public

Public methods are the generally considered the easiest to understand out of the three.

What are they?

Public methods are methods that are accessible both inside and outside the scope of your class. Any instance of that class will have access to public methods and can invoke them.

How do you use them?

You use them as you would any other method in ruby. Using dot notation, you can invoke a public method on a caller.

Let’s see it in action:

class Person
  def initialize(name, age)
    @name = name
    @age = age
  end

  public

  def name
    @name
  end

  def age
    @age
  end
end

joe = Person.new('Joe', 30)
joe.name                    # => 'Joe'
joe.age                     # => 30
Enter fullscreen mode Exit fullscreen mode

You can see above that joe is an instance of class Person. Since name and age are public instance methods, the joe object can access them even outside the scope of class Person.

As a side-note: you don’t need to preface public instance methods with public like I did in the code snippet above. All instance methods are public by default. I just did that to make it more clear.

When do you use them?

You certainly want to use public methods when displaying information for everyone to see.

A public method in human form.A public method in human form.

You should also keep a method public if it is a part of that class’s interface. Meaning that other classes interact with that class via that method. Since that last sentence made no sense, here’s an example:

class Dog
  attr_reader :name

  def initialize(name)
    @name = name
  end

  def speak
    'Arf!'
  end
end

class Owner
  attr_reader :name, :pet

  def initialize(name, pet_name)
    @name = name
    @pet = Dog.new(pet_name)
  end

  def call_pet
    puts "#{name}: Hey #{pet.name}! Come here boy!"
    puts "#{pet.name}: #{pet.speak}"
  end
end

tom = Owner.new('Tom', 'Rufus')
tom.call_pet  

# => "Tom: Hey Rufus! Come here boy!"
# => "Rufus: Arf!"
Enter fullscreen mode Exit fullscreen mode

The above example is a bit long, but you can see that the speak method in the Dog class is part of its interface. The Owner class uses that method in it’s call_pet method to allow the pet to speak. Also, we could use *speak *method by itself if we want the dog object to speak on it’s own. Therefore we should keep it a public method

It’s also important to keep your method public when you want the program/client to be able to change the state of your object. For instance, say you have a car object and you want to spray paint it a different color:

class Car
  def initialize(color)
    @color = color
  end

  def spray_paint(new_color)
    @color = new_color
  end

  def color
    @color
  end
end

my_car = Car.new('pink')
my_car.color               # => 'pink'

my_car.spray_paint('black')
my_car.color               # => 'black'
Enter fullscreen mode Exit fullscreen mode

Private

Private methods are probably the next easiest to understand as protected are a bit more nuanced.

What are they?

Private methods are not accessible outside the scope of the class whatsoever. If you try to invoke a private method with a class object, an error will occur.

class TopSecret
  private


  def data
    "Top secret information"
  end
end 

secret_file = TopSecret.new
secret_file.data           
# => NoMethodError: private method `data' called for #<TopSecret:0x007f8051041ab8>
Enter fullscreen mode Exit fullscreen mode

How do you use them?

You may have also noticed that in order to make a method private, you have to preface it with the keyword private. *Any methods that you create underneath *private will be considered private methods and off limits.

(private is actually a ruby method itself, but it may be easier to think of it as a keyword/section divider)

Furthermore, private methods do not use dot notation and can only be called implicitly. This means that you will never reference self, or any other class instance, on a private method.

If you want to use a private method within the proper scope, just call it by its method name. Simple as that.

class Mathy
  def output_result(num1, num2)
    puts crazy_algorithm(num1, num2)
  end

  private

  def crazy_algorithm(num1, num2)
    value = num1 * num2
    value *= (num1 + num2)
    value /= num1
    value -= num2 / num1
    value**2
  end
end

equation = Mathy.new
equation.output_result(3, 5)    # => 1521
Enter fullscreen mode Exit fullscreen mode

When do you use them?

Private methods are very useful when it comes to managing the interface of both your class and your program. They should be used when dealing with:

  • Sensitive information, such as passwords, personal information, etc.

  • Methods that serve as the inner workings of your class and don’t output or display information.

  • Methods that don’t add to the interface and do not collaborate with other classes.

If you’re a developer building a video game app, you wouldn’t want the player to have access to the game settings, computer AI, or any other of the inner functions.

What happens when you don’t use private methodsWhat happens when you don’t use private methods

What about when you want certain people to access the information, but not everyone? That’s when you can use a public method to act as a go-between. This allows you to create a filter that only lets some people through. A password is a good example.

class TopSecret
  def access_data    
    puts "Please enter password:"
    password_attempt = gets.chomp

    if password_attempt == '12345'
      puts data
    else
      puts 'Nice try, bozo.'
    end
  end

  private

  def data
    "Top secret information"
  end
end

secret_file = TopSecret.new
secret_file.access_data
Enter fullscreen mode Exit fullscreen mode

In the above example, the data method is private so neither the client nor the program has access to it. But a secret is only fun if some people have access to it and they can shove it everyone else’s face, so I created a accessor method called access_data.

The access_data method requires a password from the user before it gives up its secrets. A wrong password attempt leaves them being called a bozo.

Protected

We now come to the protected instance methods, the odd one of the group.

What are they?

Protected methods are a balance between public and private methods. They are similar to private methods in that they cannot be accessed in the public scope. Neither the client nor the program can invoke them.

class Person
  def initialize
    @age = rand(50)
  end

  protected

  def age
    @age
  end
end

me = Person.new
me.age          # => NoMethodError: protected method `age' called
Enter fullscreen mode Exit fullscreen mode

However, objects of the same class can access each other’s protected methods.

class Person
  def initialize
    @age = rand(50)
  end

  def >(other_person)
    age > other_person.age
  end

protected

def age
    @age
  end
end

me = Person.new
you = Person.new

me > you         # => true
Enter fullscreen mode Exit fullscreen mode

The ages are generated at random so the result of the above code won’t always be “true.” However, it will never produce an error, which is the most important take away.

Since you *and *me are objects of the same class — Person — they can access each other’s protected methods without issue.

How do you use them?

If you want to make a method protected, you must declare the protected *method first, similar to private methods. However, with protected methods, you *can *use dot notation and call **self* or another receiver.

class Person
  def younger_than?(other_person)
    self.age < other_person.age
  end

  protected

  def age
    @age
  end
end
Enter fullscreen mode Exit fullscreen mode

When do you use them?

The best time to create a method under the protected heading is when you want to compare and/or transfer data between objects of the same class, but you don’t want that information to be made public.

Say I’m building a banking game and I want to keep the account balance information private. However, I want to include features that let me know whether I have more money than a friend and to steal from them.

class BankAccount
  def initialize
    @balance = rand(100)
  end

  def richer_than(other_account)
    if balance > other_account.balance
      puts "You're rich!"
    else
      puts "Better luck next time."
    end
  end

  def steal_from(other_account, amount)
    if amount > other_account.balance
      puts "Hold it there, they aren't that rich."
      return
    else
      @balance = @balance + amount
      other_account.balance = other_account.balance - amount
      return
    end
  end

  protected

  def balance
    @balance
  end

  def balance=(num)
    @balance = num
  end
end

my_account = BankAccount.new
friend_account = BankAccount.new

my_account.richer_than(friend_account)
# => "Better luck next time."

my_account.steal_from(friend_account, 25)

my_account.richer_than(friend_account)
# => "You're rich!"

my_account.balance    # => NoMethodError
Enter fullscreen mode Exit fullscreen mode

Let’s break down the above code.

I made the balance getter method protected, so it is not available to the public scope and there’s no way of actually knowing how much money is in the account.

The richer_than method takes another BankAccount object as an argument and lets you know if your own account has more money than theirs.

The steal_from method takes two arguments, a BankAccount object and how much money to take from it. However, since you don’t know how much money the other bank account has, you have to make a guess of how much to take. That is why it lets you know if you’ve taken too much.

Finally, I invoke the getter balance method at the end just to reiterate that calling a protected method will result in an error.

Summary

Public instance methods:

  • Accessible from global scope.
  • Part of the class interface.
  • All instance methods are public by default.
  • Use if displaying information or interacting with other classes and/or the client.

Private instance methods:

  • Accessible only from within class scope.
  • Part of the class inner-workings.
  • Must preface with private method.
  • Use when hiding sensitive information or crucial class methods.

Protected instance methods:

  • Accessible from within class scope and by objects of the same class.
  • Must preface with protected method.
  • Use when wanting to compare/interact with other class objects, but don’t want the program or client to have access to them.

There you have it. An in-depth look at public, private and protected methods. I hope this has been as helpful to you as it has been to me.

Now go and make some awesome object oriented programs!

Discussion (1)

Collapse
lietux profile image
Janne "Lietu" Enberg

The answer is simple: if you ever think of using private you are wrong, and you should change your mind.

Languages with private are a whole lot more annoying to work in, because of people who use private think they're smart enough to make that decision. Then there's a bug, that could easily be fixed by extending the class and calling a protected function, or similar and instead you have to fork their code to fix it, and if the dependency tree is large enough it becomes pretty much impossible. Sometimes you can at least use reflection to call "private" functions regardless.

This has happened countless times over an extended period of time and will never cease to happen, because people will never stop thinking they are smarter than they are.

Simply put, imo private keyword should not exist in any languages, and protected should be changed to mean "you should not normally call this, but call it if you think you really need to".