Quick Introduction to Object Oriented Programming
The object oriented programming concept is rather old, it first appeared in the 1970s from the Smalltalk language, and it describes the use of objects and messages in a computer program. Basically the objects are the actors of the movie (the program), and the messages form the script.
The most popular languages used for web development support object oriented programming (OOP), being Ruby a pure object oriented language from the beginning. So I will use it to show you the OOP concepts in this article.
Class
We can understand a class as something that describes a concept. For instance, let’s suppose we have a computer class that describes how a computer is. I will use Von Neumann’s architecture:
Essentially (and to make things simple) a computer should have a CPU, memory, and input/output devices. Let’s write a class:
class Computer
attr_reader :cpu, :memory, :input, :output def initialize
@cpu = "Generic CPU"
@memory = "Generic memory"
@input = "Generic input"
@output = "Generic output"
end def to_s
"Computer specs: \n" +
"CPU: #{cpu} \n" +
"Memory: #{memory} \n" +
"Input device: #{input} \n" +
"Output device: #{output} \n"
end
end
The parts of our generic computer are called properties. The initialize method is the constructor, because it’s called automatically when the class is instantiated (more on this in the next section). The to_s method is special as well, you will see why in the next section.
Object
Because a class is only describing something, we need to instantiate it into an object to actually use that description.
We already have a computer description, we know how a computer is. So if we buy one, we will have our computer. Let’s buy it:
my_computer = Computer.new
Yay! Now we can inspect our computer:
puts my_computer
When defining the to_s method, it will be called automatically by Ruby when displaying the object itself. So in this case it will be called and it will display this information:
Computer specs:
CPU: Generic CPU
Memory: Generic memory
Input device: Generic input
Output device: Generic output
Yeah, we have a generic computer. But no one has a generic computer. Every computer has computer parts built by a brand. And that brings us to the next concept.
Inheritance
This concept can be understood as a relationship between two classes. One is the parent, and the other one is the child.
In Ruby, a parent can have multiple children, but a child can only have one parent. There are other languages where a child can have multiple parents (multi-inheritance).
The child will inherit all properties and methods from the parent.
Let’s build a Macbook Pro:
class MacbookPro < Computer
def initialize
@cpu = "Intel Core i5"
@memory = "8 GB DDR3"
@input = "Keyboard"
@output = "13-inch with Retina display"
end
end
Here we have inherited everything from the Computer class, but we have defined the initialize method again. This action is known as override, we have overridden the constructor.
Now when we instantiate an object and display it, the to_s method will be called, and because we have not overridden it, the Computer code will be taken.
my_macbook_pro = MacbookPro.new
puts my_macbook_proComputer specs:
CPU: Intel Core i5
Memory: 8 GB DDR3
Input device: Keyboard
Output device: 13-inch with Retina display
From this point we could customize our Macbook Pro adding more input and output devices, but let’s keep it simple.
Ruby has a mechanism that can be used to replicate multi-inheritance: modules. So let’s create a module for portable computers:
module PortableComputer
attr_reader :battery @battery = "Generic battery"
end
Now I will include the brand new PortableComputer module, initialize the battery property and override the to_s method of our Macbook Pro class:
class MacbookPro < Computer
include PortableComputer def initialize
@cpu = "Intel Core i5"
@memory = "8 GB DDR3"
@input = "Keyboard"
@output = "13-inch with Retina display"
@battery = "Built-in 74.9-watt-hour lithium-polymer"
end def to_s
super +
"Battery: #{battery} \n"
end
end
Hey wait! What’s that super thing? It is used to get the parent’s code. So in this case I’m getting all the to_s code, and then appending the battery string.
Running our code again should display this:
Computer specs:
CPU: Intel Core i5
Memory: 8 GB DDR3
Input device: Keyboard
Output device: 13-inch with Retina display
Battery: Built-in 74.9-watt-hour lithium-polymer
Encapsulation
Did you notice how we displayed the specs? We just called puts followed by the object. Ruby called to_s automatically and the specs were displayed.
The way we built the output string inside the method gives the class the encapsulation concept, because when we use puts, we don’t know how to display that information, we just call the object and that’s it. It’s the responsibility of the class to do it, so using the object is simple and straightforward.
So encapsulation can be understood as hiding the internal representation of the object (our to_s method implementation) from the outside (when using the object).
Let’s suppose that Apple let us to upgrade our Macbook Pro’s memory. This can be achieved by implementing a new method in the class:
class MacbookPro < Computer
# all the existing code def upgrade_memory
@memory = "16 GB DDR3"
end
end
Now from the outside we can upgrade memory just calling that method. We don’t need to know how to do it, so we achieved encapsulation. Let’s see it in action:
my_macbook_pro = MacbookPro.new
my_macbook_pro.upgrade_memory
puts my_macbook_pro
It should display 16 GB of memory now.
Polymorphism
Polymorphism is when we provide a common action for different classes. While the action is apparently the same, it has a different implementation for each class.
Let’s come back to our example and tweak our Macbook Pro’s upgrade_memory method:
class MacbookPro < Computer
# all the existing code def upgrade_memory
puts "Sorry, memory is soldered and cannot be upgraded."
end
end
Now let’s create a new computer, a 27-inch iMac:
class IMac27 < Computer
def initialize
@cpu = "Intel Core i5"
@memory = "8 GB DDR3"
@input = "Keyboard"
@output = "27-inch with Retina display"
end def upgrade_memory
@memory = "16 GB"
end
end
Instantiate and call the objects like this:
my_imac_27 = IMac27.new
my_imac_27.upgrade_memory
puts my_imac_27putsmy_macbook_pro = MacbookPro.new
my_macbook_pro.upgrade_memory
puts my_macbook_pro
This is the output:
Computer specs:
CPU: Intel Core i5
Memory: 16 GB
Input device: Keyboard
Output device: 27-inch with Retina displaySorry, memory is soldered and cannot be upgraded.
Computer specs:
CPU: Intel Core i5
Memory: 8 GB DDR3
Input device: Keyboard
Output device: 13-inch with Retina display
Battery: Built-in 74.9-watt-hour lithium-polymer
The computer specs are from our iMac. We can see that memory was successfully upgraded. But then we could not upgrade our Macbook Pro’s memory. It’s displaying our error message, and then the specs still show 8 GB.
Conclusion
I encourage you to modify the sample code I have used for this article. Play with it and try to break things. It’s a good way to consolidate your new knowledge and understand it even better.
Although I have used Ruby, the concepts are basic and are the same for the rest of languages.