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.
Top comments (22)
Although encapsulation and inheritance are quite dead and buried.
what do you mean by "dead"?
Well, in the case of inheritance it's been widely considered an anti pattern for years now. In a way it's "deprecated" in favor of composition (composition over inheritance). Mostly because it tightly couples your objects in a tangled hierarchy that prevents useful reusability.
In the case on encapsulation, it turns out it makes little sense. Bind data and operations? Data is inert, it doesn't have agency. It's not a logical representation of concepts that you can work with. A chair doesn't change itself. But on a practical level, mutable states are an enemy of stability and predictability in code. That's why immutability has continuously grown in popularity as a practice and is a core concept of DDD. But if immutability is preferred, why encapsulate at all? You're not changing the bound (current) instance so you might as well cleanly pass data to a pure function and receive a changed dataset. And lastly, just about any OOP language provides reflection mechanisms that can be used to break encapsulation (so it's not much of a guarantee of anything).
There's a fairly famous quote about the downsides of inheritance (and it also hints at encapsulation as well):
“… Because the problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle. “ —Joe Armstrong, creator of Erlang
I'm afraid in 2021 Erlang is a lot more "dead and buried" than encapsulation and inheritance. There's probably a good reason why it isn't in IEEE's top 50 programming languages, unlike Python, Java, C#, JavaScript and other gorilla-feeding jungles.
Functional programming is trendy and "beautiful" and "pure". OOP pays the bills.
Sure, but it's OOP that advocates composition over inheritance as well as immutability nowadays. It has nothing to do with functional programming, just a note that FP can do immutability better.
Also a note: unlike Java and C# which are fundamentally OOP, Python and Javascript are true multi paradigm languages - they can do FP just as much as the next.
In development, things tend to change.
"Advocates" is almost but not quite entirely unlike "deprecated". OOP provides different tools for different use cases and it's the developer's responsibility to use them appropriately. This is like "advocating" hammers over wrenches. Immutability is as useful as nails but there are situations where screws work better. FP has become Maslow's hammer of programming these days.
My opinion is best expressed by Steven Lowe here
I'm sure (though have no data) that FP's invasive expansion into JS and Python correlates to the number of Erlang experts getting real-world jobs.
Not sure how you can argue FP "invasion" into Python and JS as neither were designed as OOP languages. Python grew into both little by little but Javascript was function first and only later sort of squeezed the idea of objects and even later provided the peppers means for using core concepts like encapsulation.
Neither was initially designed as FP. The use of the keyword "function" doesn't make a programming language "functional". I'm not an expert on Python but I've worked with JS since it was called Netscape Mocha/LiveScript. With the exception of functions as first-class objects (which few treated as such at the time) it was as procedural as FORTRAN. On the other hand, it was created with Sun's cooperation and under the strong influence of Java (and OOP in general) from day one. So yes, I can argue all day that FP in JS is a recent "invasion".
But that's largely irrelevant. My point is, inheritance and encapsulation are far from "dead and buried", they are important concepts which can be useful and profitable.
Nobody said they were design as FP. I say that JS was function-first as that was the main construct. Sure, you can say functions as first-class objects, which is truly a painful twist of concepts if that's what you want to see. I know people really wanted to force JS constructs into object-friendly usage patters (prototype, anyone) but it's a stretch to say there were Java "strong" influences back in the day. Objects are Java's code and there were no such things then (scoped variables protected/private/etc and funny inheritance was actually a composition of functions).
Thanks for sharing that article, though from my perspective it basically enforces my point. Inheritance defines relationships between objects (which prevents code reuse outside of a given project) and moreover, the author makes a great point that there's a right way to use inheritance. But that definition is his own convention outside if the definition of inheritance itself. Almost every developer of a given seniority has a theory and practice about the "right" way to use, despite the shortcomings on reliability and maintainability - some of which comes exactly from the need to enforce a set of arbitrary conventions about the right way to tackle inheritance. You need but a slip of code review to go down a slippery slope towards messiness (not to mention once you setup a hierarchy, nobody will ever be able to change it - there's a basic tenet to uphold agility in development practices that says "if you're face with two solutions to a problem, go with the one that's easier to change later on").
I've been in the industry for a decade and still need to review from time to time. That said, great work, and I'm keeping this article in my favorites!
Thanks, appreciate it :D
I really enjoyed reading this.
love how you broke it down
Thank you.. very informative!
I have a question on your given line
"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."
My questions are given below:
How player accessed the client because it is an application and the code is generated already and the system just call the instance?
How hacker will access and alter this code? Because the application is generated an instance of this class and just run only.
This was hypothetical, but it may be possible to inject java code to some game but idk, I used this example to explain encapsulation.
Great writing! Can't go wrong with the basics (and I forgot the basics all the time :D)
Great article! Got my like :)
Thanks :D
Side question:
Do you think that being able to add function insides to interface method declaration through keyword default
default void printHi() {
sout("Hi!");
}
makes abstract classes kinda useless?
excellent overview. nice read for someone trying to brush up on these concepts!
Thanks for refreshing my knowledge