Background
In a recent interview, I’ve been asked what object-oriented programming principles are and to give the interviewer some examples. Though object-oriented programming principles are almost one of the common interview questions, sometimes it’s challenging to provide the interviewer examples right at that moment (sadly, especially if you are nervous).
So, I decided to brush up on my memory of the 4 OOP principles using some examples that I worked through when I was in school.
Let’s get into it!
NOTE: Please note that the scope of this article is to understand what the 4 OOP principles are. Not to list the advantages or disadvantages of them.
4 OOP Principles
Here are the four principles:
1. Abstraction
2. Polymorphism
3. Inheritance
4. Encapsulation
If you shall make it as an acronym, “A PIE”!
Game Of Life
For the purpose of explaining these OOP principles, we are going to use “Game-Of-Life”. If you are not familiar with “Game-Of-Life”, it’s a game invented by John Conway, a mathematician. The details of the game can be found on this Wikipedia. You can also play the game from this site. However, in this article, we will make a bit of a change to the original game.
Game Set Up and Rules
If you are curious about the modified game set-up and rules, you can read the section “Game Setup and Rules” in this GitHub repo.
However, you should be able to understand the content of this article without reading the game setup and rules.
Code Examples
If you want to see the full code, please visit the Game-Of-Life repo.
Abstraction
As the word suggests, abstraction means “Show the high-level only”. We are only interested in what we can use, not how.
The abstraction is a process of hiding the implementation details and showing what functionalities we can use.
Abstraction can be achieved using the abstract
keyword on classes or methods, or the interface
.
We will be focusing on using the abstract
keyword in this article. The interface
does the same thing as the abstract
keyword, however, it forces you to implement every function in subclasses that implement the interface
.
LifeForm.java
Herbivore.java
Here, the LifeForm
is defined with the abstract
keyword and the Herbivore
is extending the LifeForm
. The LifeForm
is showing what methods (performAction
, countEdible
, and createLife
) can be used and the Herbivore
is showing how the methods can be used by providing the actual implementations. So, we can say (with confidence!) the LifeForm
is the “high-level” of the Herbivore
and that the abstraction has been applied to our code.
Polymorphism
The word “poly” means “many” and “morph” means “changing forms”. As these two words indicate, Polymorphism means that it can have multiple forms.
Ok, great, but how can it be achieved in Java?
Polymorphism can be achieved in two ways – method overriding
and method overloading
.
Let me explain what method overriding
and method overloading
are with some code examples.
Method Overriding
Method Overriding is quite literal. A method from the superclass can be “overridden” by a method from the subclass using the same method name.
LifeForm.java
Carnivore.java
Omnivore.java
The method createLife(Cell cell)
in the LifeForm
class is being overridden by the method createLife(Cell cell)
in Carnivore
or Omnivore
classes. Notice that the methods have the exactly same name with a parameter.
NOTE: Don’t be afraid of the abstract
keyword in front of the createLife(Cell cell)
method. It still works the same as other regular methods. It is still method overriding
.
Method Overloading
In our code, we are not really using method overloading, however, let’s make an assumption and imagine we have the code below.
LifeForm.java
In the code above, we are able to find two methods with the same name but different parameters. Even though they have the exact same name, it’s not a problem for Java to figure out which one to use as long as the user provides the correct parameters.
TIP: If you have a hard time remembering what method overloading is just by looking at the term, you can think of a method getting “overloaded” with parameters.
Inheritance
Inheritance is pretty straightforward. It means that the child class(s) (a.k.a. subclass) inherits a parent class (a.k.a. superclass). A child class inherits all fields, methods, and nested classes with the default(no-modifier)/protected/public access modifier of the parent class.
Inheriting Attribute
The attributes from the superclass can be accessed/used from subclasses by using the keyword super
.
LifeForm.java
Herbivore.java
Whenever Herbivore
object is created, it will store “cell” information passed as an argument. You might have noticed that we actually don’t have “cell” attribute in the Herbivore
class. Even though there is no “cell” attribute in the Herbviore
, we are able to store cell information in the “cell” attribute in the LifeForm
by using super.cell = cell
.
Inheriting Method (a.k.a. method overriding)
The methods can also be inherited from superclass to subclasses. A method in subclasses can override the implementation of the method that has the same name in the superclass.
LifeForm.java
Plant.java
Carnivore.java
NOTE: I made a slight change in LifeForm
from the original code to explain one more detail.
The method performAction
is being overridden by the subclasses Plant
and Carnivore
. When the method performAction
is invoked, each lifeform would act differently. A Plant
object will give birth, on the other hand, a Carnivore
object will move first and then give birth. If you really want to invoke performAction
method from LifeForm
, you will be able to do so by doing super.performAction()
in the subclasses.
As another example, the Plant
and Carnivore
subclasses inherit the method createLife
from LifeForm
. However, the actual implementation in both subclasses is different. It creates a new Plant
object in the Plant
subclass and a new Carnivore
object in Carnivore
subclass.
If you made it here, great job on following through!
Let’s take a look at our last principle “Encapsulation”.
Encapsulation
Again, Here is one of the definitions of Encapsulation on Google – the action of enclosing something in or as if in a capsule.
Imagine a capped pill with a capsule that contains powder medicine inside. If we don’t have a capsule protecting the powder, it will be blown away in a second. It’s the same concept in computer science as well. We encapsulate class members (whether it’s a variable or data) within that class.
By implementing encapsulation, we get the benefits of hiding and protecting fields from the outside of the class.
Let’s take a look at an example that doesn’t use encapsulation.
If a field has the public
access modifier, then we would be able to access a field by creating an object of that class and using dot notation. However, it is never a good practice to overuse the public
access modifier. It is always recommended to use the private
access modifier and “encapsulate” the data within the class to keep the fields safe.
If we change the public
access modifier to the private
access modifier, we will be no longer able to access the field using dot notation. Then, how would we access the field in this case? We will create methods for reading and writing the field (a getter for reading and a setter for writing).
LifeForm.java
There are 2 fields defined as private
– isAlive
and moved
. Since they have the private
access modifier, they can’t be accessed directly from outside (i.e. lifeform.isAlive
won’t work). For that reason, we created getters and setters for the fields. However, notice that we only have the setter for isAlive
. This means isAlive
will be “write-only”. This is one of the benefits of using encapsulation. We can pick and choose what to expose to the outside world.
Now, this concludes all 4 Obejct-Oriented Programming principles in Java.
Hopefully, the explanation with code examples helped you understand what they are and how they can be implemented.
Thank you for reading. If you have thoughts on this, be sure to leave a comment.
I also write articles on Medium, so make sure to check them out on medium/@codingnotes.
Happy coding!