Object-Oriented Programming: Encapsulation and Inheritance
- 03 January 2017
- Ruby Design
- Object-Oriented Programming: Encapsulation and Inheritance
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 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 (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"
Human is a parent class for
Woman are inherited from
Human, they have all methods that
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 -
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.
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.
power method for Superman we want to get value of
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
powerin object of class
Superman, which inherited from
- inside method
power, we called method
superwhich called following method in parent class (
- 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
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
In interviews people like to ask this tricky question: what's the difference between
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
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
(), 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!