OOP Principles For Dummies
Tamerlan Gudabayev
Posted on March 21, 2021
I don't have much experience but been in a couple of interviews.
They seem to always ask the same cliche questions.
For example:
Where do you see yourself in five years?
I usually have answers prepared beforehand, for these types of questions.
But there is one question, that I know will come up and I keep messing it up.
It is:
What are the main principles of Object-Oriented Programming?
It's pretty much a universal question, talked about in all sorts of technical interviews, from junior to senior. Interviewers ask this type of question because it is an easy way to gauge the interviewee's experience and level of understanding.
It helps to tell three things:
-
Did the interviewee prepare for the interview?
If you hear an answer immediately, it's usually a good sign.
-
Does the interviewee think properly before he writes code?
Understanding and applying the principles of OOP shows that the candidate is beyond the point of hack and slash coding. He doesn't just copy code from the internet, he has structure in his code and way of thinking.
-
Is the interviewee understanding deep or shallow?
This is a very open-ended question, you can go as shallow or as deep as you can. Bonus point if you go deep.
The four principles object-orientated programming are encapsulation, abstraction, inheritance, and polymorphism.
I know it sounds very intimidating but trust me, it's not.
It's much simpler, and that's why I want to give you simple, straight-to-the-point explanations with examples on each.
Encapsulation
Encapsulation binds data and it's related methods together within a class. It also protects the data by making fields private and giving access to them only through their related methods.
Imagine we have a small program.
It has a few objects which talk to each other, within the rules defined in the program.
Encapsulation is achieved when each object keeps its state private meaning that no outside class can directly change it. Instead, they can only call a list of public function — called methods.
Before we move on we have introduced some keywords which you may be confused about, let me explain:
- State: The state represents the data (value) of an object. Suppose a bank account is an object then Account Number, Amount, etc... will be the states of my bank account.
- Private: Data that is can only be accessed within the class.
- Protected: Data that can only be accessed within the class, and it's subclasses.
- Public: Data or functions (methods) which can be accessed outside of the class.
Violation of Encapsulation
Imagine that we have a single-player role-playing game with a class that manages quests called Quest
.
public class Quest {
// possible options for status
private static final int NOT_PICKED = 0;
private static final int PICKED = 1;
private static final int COMPLETED = 2;
// state
public String title;
public int reward;
public int status;
// Constructor
public Quest(String title, int reward, int status){
this.title = title;
this.reward = reward;
this.status = status;
}
// Other behavior
}
Our client code will look something like this:
public static void client() {
Quest quest = new Quest('Goblin Slaying', 190, 3);
}
Imagine that one of the players got access to this client, and wanted to cheat by changing the game status and reward.
public static void client() {
Quest quest = new Quest('Goblin Slaying', 190, 3);
quest.reward = 1000000000;
quest.status = 5;
}
Right now this code is valid, because our variables are publicly accessible. Another problem here is that our "hacker" set the status to 5 which doesn't exist, and thus our game breaks.
Fixing Our Example
The way to fix this is to simply make all our variables private and only accessible through their respective methods.
public class Quest {
private static final int NOT_PICKED = 0;
private static final int PICKED = 1;
private static final int COMPLETED = 2;
private String title;
private int reward;
private int status;
public Quest(String title, int reward, int status){
this.title = title;
this.reward = reward;
this.status = status;
}
public String getTitle() {
return title;
}
public int getReward() {
return reward;
}
public int getStatus() {
return status;
}
public void setQuestNotPicked(){
this.status = NOT_PICKED;
}
public void setQuestPicked(){
this.status = PICKED;
}
public void setQuestCompleted(){
this.status = COMPLETED;
}
// other behavior
}
With these changes now, we cannot change the reward and title. But we can get them through their respective functions.
Finally, our code is "hacker" safe.
Benefits of Encapsulation
- Encapsulation protects an object from unwanted access by clients.
- Encapsulation allows access to a level without revealing the complex details below that level.
- It reduces human errors.
- Simplifies the maintenance of the application
- Makes the application easier to understand.
Tips on encapsulation
For the best encapsulation, object data should almost always be restricted to private
or protected
. If you choose to set the access level to public
, make sure you understand the consequences of the choice.
Abstraction
Abstraction is the concept of object-oriented programming that "shows" only essential attributes and "hides" unnecessary information.
Abstraction is an extension of encapsulation, but what's the difference?
-
Encapsulation - means hiding data like using
getter
andsetter
etc. - Abstraction - means hiding implementation using abstract class and interfaces etc.
But now I'm even more confused, what are abstract classes and interfaces?
Abstract Class
An abstract class is a simply a normal class that has some special exceptions:
- An abstract class may have methods that do not have a body
- An abstract class has to be sub-classed, at some level, to a non-abstract class before you can instantiate an object
- It is not allowed to directly instantiate an object of an abstract class
So, why would I ever use such classes?
Well, they allow us to do multiples things:
- Consolidate common behavior (unlike an interface which only defines the contract)
- Provide default (and optionally override-able) implementations for functions
Let's look at a simple example:
//abstract parent class
abstract class Animal{
//abstract method
public abstract void sound();
}
//Dog class extends Animal class
public class Dog extends Animal{
public void sound(){
System.out.println("Woof");
}
public static void main(String args[]){
Animal obj = new Dog();
obj.sound();
}
}
Abstract class vs Concrete class
- An abstract class is useless until it is extended by another class.
- If you declare an abstract method in a class then you must declare the class abstract as well. You can’t have an abstract method in a concrete class. Its vice versa is not always true: If a class is not having any abstract method then also it can be marked as abstract.
- It can have non-abstract method (concrete) as well.
Interfaces
An interface is an abstract type that is used to specify a behavior that classes must implement.
Think of interfaces as a contract.
You have a bunch of rules which you must follow through but the details don't really matter.
For example, we have a contract for the behavior of a dog called DogInterface
it will look something like this:
public interface DogInterface {
public void bark();
public void wiggleTail();
public void eat();
}
Here we are saying that every class that implements the DogInterface
must have these three methods. As you can see there is absolutely no implementation details, it doesn't say how should a dog bark, wiggle its tail or eat. Having said that, we now have the ability to create different dog classes that all follow the same contract.
public class LabradorRetriever implements DogInterface {
public void bark() {
System.out.println("Woof, woof!!");
}
public void wiggleTail() {
System.out.println("wiggles tail");
}
public void eat() {
System.out.println("eats food");
}
}
You might ask "Why would I ever want to use an interface?"
Interfaces in a nutshell guarantee that a class will have x methods.
This may sound too simple, but it actually is just that.
Nonetheless this small feature helps us tremendously, imagine we have a shipping service program and our client code uses the Car
class to transport stuff. After some time our program gets popular and we know need to support planes, trains, ships, trucks, etc...
One way to do this is to create a common interface called TransportInterface
that will be used by all the transport vehicles (airplane, car, etc...). This way our client code will not have to worry if the class Airplane
will not have some common method.
Which to use? Abstract Classes or Interfaces?
As always it depends.
There are many different factors to consider.
But in general, an abstract class is used when you want a functionality to be implemented or override in subclasses. On the other hand, an interface will only allow you to describe functionality but no implementation. Also in most programming languages, a class can only extend one abstract class, but it can take advantage of multiple interfaces.
Coming back to abstraction
This post definitely took a turn, but it was necessary to talk about abstract classes and interfaces.
In a nutshell:
Abstraction is an extension of encapsulation where it literally hides implementation using either abstract classes or interfaces.
Inheritance
Inheritance is the mechanism of basing an object or class upon another object or class, retaining similar implementation.
A common problem in programming is that objects are too damn similar.
Objects are often very similar, have common functionality but they are not entirely the same.
Now comes the dilemma that every programmer goes through.
"Do I create an entirely new class or put all common functionality in a base class?"
I'm not gonna say which option is better, but we are talking about OOP today. That means we are gonna pick the second option, creating a base class that will store all common functionality.
School Example
Imagine we were tasked to create a school management system and we have two entities called Student and Teacher. In our first version of the app we implemented it them like this:
public class Student {
private String name;
private String last_name;
private String gender;
private String classroom;
private String year;
public void attendClass(){
System.out.println("Attend Class");
}
public void eatSandwich(){
System.out.println("Eating a sandwich");
}
}
public class Teacher {
private String name;
private String last_name;
private String gender;
private String schedule;
private String subject;
public void teachClass(){
System.out.println("Teaching a class");
}
public void eatSandwich(){
System.out.println("Eating a sandwich");
}
}
As you can see there are many common states and behaviors, how about we extract all that to a common class called Human
public class Human {
protected String name;
protected String last_name;
protected String gender;
public void eatSandwich(){
System.out.println("Eating a sandwich");
}
}
PS. I set all my variables protected for them to be accessible in subclasses.
With this we can inherit all common states and behaviors into our Student
and Teacher
class.
public class Student extends Human {
private String classroom;
private String year;
public void attendClass(){
System.out.println("Attend Class");
}
}
public class Teacher extends Human {
private String schedule;
private String subject;
public void teachClass(){
System.out.println("Teaching a class");
}
}
Congratulations we have removed duplicated code, and that is a good sign.
Coming back to inheritance
Inheritance isn't that simple, there are actually different types of inheritance.
- Single Inheritance: This is the simplest kind of inheritance where we inherit from a parent class. Just like our example above with teachers and students.
-
Multi-Level Inheritance: An inheritance becomes multi-level when it inherits from a subclass. For example, let's imagine from our example above that we added a class called
Intern
. Intern has the same state and behavior asTeacher
but with some extra fields, so we simply inherit fromTeacher
. This is multiple inheritances because now classIntern
inherits from bothTeacher
andHuman
. - Multiple Inheritance: Happens when a class inherits more than one parent class. This is usually not allowed in most programming languages but is available in some.
-
Hierarchical inheritance: Is when multiple child classes inherit from the same parent class. For example we have a class
Dog
andCat
that both inherit from classAnimal
. - Hybrid Inheritance: When there is a combination of more than one form of inheritance, it is known as hybrid inheritance.
Benefits of Inheritance
- Code reusability
- One superclass can be used for the number of subclasses in a hierarchy.
- No changes to be done in all base classes, just do changes in parent class only.
- Inheritance avoids duplicity and data redundancy.
- Inheritance is used to avoid space complexity and time complexity.
Precaution
Now I am not saying to always extend your classes, doing that will make your code very coupled together. Another approach would be to use composition or aggregation.
You can read more about it here
Polymorphism
Polymorphism is the ability of an object to take on many forms.
Let us break down the word:
- Poly = many: polygon = many-sided, polystyrene = many styrenes , polyglot = many languages, and so on.
- Morph = change or form, morphology = study of biological form, Morpheus = the Greek god of dreams able to take any form.
So polymorphism is the ability (in programming) to present the same interface for differing underlying forms (data types).
The classic example is the Shape
class and all the classes that can inherit from it (square, circle, dodecahedron, irregular polygon, splat and so on).
With polymorphism, each of these classes will have different underlying data. A point shape needs only two co-ordinates (assuming it's in a two-dimensional space of course). A circle needs a center and radius. A square or rectangle needs two coordinates for the top left and bottom right corners and (possibly) a rotation. An irregular polygon needs a series of lines.
By making the class responsible for its code as well as its data, you can achieve polymorphism. In this example, every class would have its own Draw()
function and the client code could simply do:
shape.Draw()
to get the correct behavior for any shape. This is in contrast to the old way of doing things in which the code was separate from the data, and you would have had functions such as drawSquare()
and drawCircle()
.
Types of Polymorphism
Usually in interviews they ask questions like:
"What is polymorphism?"
You answer them properly, then to throw you off your game they ask:
"What are the different types of polymorphism?"
Now you have two options:
- Answer the question cause your smart.
- Make up some stuff.
Unfortunately, I had this same question asked me, and I failed to answer. This is why we will talk about the types of polymorphism.
The two types of polymorphism are dynamic polymorphism and static polymorphism.
Dynamic Polymorphism
This is also known as:
- Run-Time polymorphism
- Dynamic binding
- Run-Time binding
- Late binding
- Method overriding
This is your standard polymorphism. It's basically when a subclass has a method with the same name and parameters of a method in a parent class, these methods are in different forms (this is known as method overriding).
For example, let us illustrate our shapes example above.
public class Shape {
protected int width;
protected int height;
// some more functionality for shape
public void draw(){
// draws a shape using the width and height.
}
}
public class Circle extends Shape {
private int radius;
@Override
public void draw() {
// draw a circle code
}
}
public class Square extends Shape {
@Override
public void draw() {
// specifically draw a square
}
}
Our client code will look something like this:
public static void main(String[] args) {
Shape shape = new Shape();
Shape circle = new Circle();
Shape square = new Square();
shape.draw(); // calls the draw method in shape class
circle.draw(); // calls the draw method in circle class
square.draw(); // calls the draw method in square class
}
As you can see, both our Circle
and Square
class inherit from Shape
class. They also override the draw()
method in Shape
class.
Static Polymorphism
This is also known as:
- Compile-Time polymorphism
- Static binding
- Compile-Time binding
- Early binding
- Method overloading
This is basically when a method has many forms in the same class.
Let's look at an example:
public class Calculator {
void add(int a, int b) {
System.out.println(a+b);
}
void add(int a, int b, int c) {
System.out.println(a+b+c);
}
}
public static void main(String args[]) {
Calculator calculator = new Calculator();
<span class="c1">// method with two parameters gets called</span>
<span class="n">calculator</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="mi">10</span><span class="o">,</span> <span class="mi">20</span><span class="o">);</span> <span class="c1">// output: 30</span>
<span class="c1">// method with three parameters get called</span>
<span class="n">calculator</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="mi">10</span><span class="o">,</span> <span class="mi">20</span><span class="o">,</span> <span class="mi">30</span><span class="o">);</span> <span class="c1">//output: 60</span>
<span class="o">}</span>
Benefits of Polymorphism
- It helps the programmer to reuse the codes, i.e., classes once written, tested and implemented can be reused as required. Saves a lot of time.
- Single variable can be used to store multiple data types.
- Easy to debug the codes.
Conclusion
The four principles of object-orientated programming are:
-
Encapsulation: means hiding data like using
getter
andsetter
- Abstraction: hides implementation using interfaces and abstract classes.
- Inheritance: mechanism of basing an object or class upon another object or class, retaining similar implementation.
- Polymorphism: ability of an object to take on many forms.
Congratulations, now you can answer one of the most asked questions in interviews and kick ass.
If you got any questions, then feel free to leave them down in the comments section below.
Posted on March 21, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.