Object-Oriented Programming: Encapsulation and Inheritance

Subscribe to receive new articles. No spam. Quality content.

Ruby - it's an object-oriented language. If we want to understand ideas that Matz put into Ruby - we should understand basics of object-oriented programming (OOP). In this post I'll cover encapsulation and inheritance. I'll devote separate post for polymorphism.

Encapsulation

Encapsulation is an Object Oriented Programming concept that binds together the data and functions that manipulate the data, and that keeps both safe from outside interference and misuse. Data encapsulation led to the important OOP concept of data hiding.

Object - it's just a state and behavior. Each class has interface - set of public methods

Encapsulation tells us that we should know just an interface of class to be able to use it. We don't have to know anything about internals of class to be able to use it.

Let's check following real-world example. The best example of encapsulation could be a calculator. We understand its interface from first sight and we don't have to know how it works inside. We know that we can press 2+2 then = and see the result on display.

In Ruby we can easily check interface of class - we should look for its public methods. Then we can find method that fits for our needs and check which arguments to pass to get result.

In statically typed languages you also have to define a type of arguments which method has as well as type for returning value.

Ruby it's a dynamically typed language, so all we can learn about method from first sights it's a name and params it accepts.

I must admit that naming for public methods and params is so important because it serves as a some sort of documentation.

Also, we should remember that class should have as less public methods as possible. It will allow to simplify using of that class and protect data from undesired changes from outer world. I have a blogpost about public, private and protected methods in Ruby. It allows you to control access level to methods. Also having less methods will help you not to break Single Responsibility rule.

Good public interface will allow you to change inner implementation of method, but keep the same interface for outer world. It's especially important if you develop open-sourced gem or application.

Inheritance

Inheritance (OOP) is when an object or class is based on another object or class, using the same implementation specifying implementation to maintain the same behavior.

Let's consider this example:

class Human
  def walk
    "Walking"
  end

  def breathe
    "Breathing"
  end
end

class Man < Human
end

class Woman < Human
end

adam = Man.new
adam.breathe # => "Breathing"

eve = Woman.new
eve.breathe # => "Breathing"

Class Human is a parent class for Man and Woman. Because Man and Woman are inherited from Human, they have all methods that Human has: walk and breathe.

There is a easy way to define if you should use inheritance or composition. It's called is-a vs has-a. We use inheritance when you can describe relation between parent and child class as is-a, for example:

  • Man - is a Human
  • Woman - is a Human

This statement we expressed in code as:

  • Man < Human
  • Woman < Human

Let's see another example. Superman - it's still human, but it has (has-a) superpower. In this case we can use composition:

module Superpower
  def fly
    "Flying!"
  end
end

class Superman < Human
  include Superpower
end

superman = Superman.new
superman.breathe # => Breathing
superman.fly # =>  Flying!

Superman is still human, he walks (walk) and breathes (breathe), but now he has (has-a) an ability to fly. That's because we included Superpower module into class.

Another example of inheritance is ApplicationController in Ruby On Rails applications. Usually developers create one controller and inherit other controllers from it. It allows to pull shared methods into parent class and don't copy/paste those methods into child controllers. This relation also fits perfectly to is-a relations. For example UsersController it's a controller of our application - ApplicationController.

Couple more words about inheritance. In some languages multiple inheritance is possible. One class can be inherited from couple more parent classes. Multiple inheritance isn't possible in Ruby. We can inherit class just from one parent class.

Imho, it was right decision by Matz, because in many cases multiple inheritance it's a bad idea which reveals problem with proper design of application. If we want to extend class by some methods, we always can use mixins and include module with those methods.

Speaking about inheritance, we have to discuss method super, which allows to call method from parent inside child class.

Example:

class Man
  def power
    10
  end
end

class Superman < Man
  def power
    super * 1.5
  end
end

Superman.new.power # => 15

Superman is inherited from Man class. Let's imagine that for regular man power is equals to 10 points. We want Superman to have x1.5 power of regular man.

In power method for Superman we want to get value of power from Man class. In this case we can use method super, which calls method in parent class. We have this chain of calls:

  • we called method power in object of class Superman, which inherited from Man.
  • inside method power, we called method super which called following method in parent class (power for Man returns 10)
  • multiplied returned value by 1.5

Here is one more example:

class Man
  attr_reader :name

  def initialize(name)
    @name = name  
  end
end

class Hero < Man
  attr_reader :hero_name

  def initialize(real_name, hero_name)
    @hero_name = hero_name
    super(real_name)
  end
end

superman = Hero.new("Clark", "Superman")
superman.name # => Clark
superman.hero_name # => Superman

Class Man accepts one argument - name. Also it has attr_reader, which makes this argument accessible . For Hero class we also have hero's name param.

So when we create new instance we pass two params. Also we should call initialize for parent class Man. If we don't do that - then @name variable will not be set, and superman.name will return nil. As we know, to call method in parent class we use super.

In interviews people like to ask this tricky question: what's the difference between super and super()?

Example:

class Man
  def initialize(name)
    puts name.inspect
  end
end

class Hero < Man
  def initialize(name)
    super
  end
end

Hero.new("Clark") # => Clark

If we call super in child-class and don't pass any params - it will use params from current method call and will pass it into parent class. In our case super call is equal to super(name). By result of execution we see that name was passed to Man#initialize.

If we want to call parent method, but don't want to pass all params from child method - we can use this call super(). This method will call parent method, but will not pass any params into that.

class Man
  def foo
    puts "I don't need params"
  end
end

class Hero < Man
  def foo(param)
    puts "I need a param"
    super()
  end
end

Hero.new.foo("Param")
# => I need a param
# => I don't need params

If we call super without (), it will try to pass param into method in parent class which will cause an error: foo': wrong number of arguments (1 for 0) (ArgumentError)

That's it on encapsulation and inheritance in Ruby. In next article I'll cover polymorphism.

PS: Thanks everyone for comments and new ideas for blogposts. I really appreciate any feedback. I'm really trying to provide value for you so let me know if you have something you want me to cover. Sometimes in discussion you get much more insights than in blogpost itself. Stay tuned!

Subscribe to receive new articles. No spam. Quality content.

Comments