Once upon a time in the OO world, Mr. Rahul, our product manager was given the opportunity to create the website for CarsInfo.com, the one-stop shop for buying and selling old cars. To encourage healthy competition within the team, he decided to give this project to two developers, Mohan and Rajesh who recently joined the company after completing their B.tech from a reputed institute.
Mohan is an OOP enthusiast and Rajesh, who believed in getting things done via the shortest path.
To make things interesting, Rahul decided to offer a promotion to the developer who can display a 3D view of all the different cars available in their store on their website.
Rajesh: “This is a piece of cake! I just need to create a Car class to store all the properties of a car and a render method for rendering that data. Look what I have done:”
Mohan, meanwhile, thought to himself, “Who are the key players here? and how will they evolve in the future?”. So he took a slightly different path and decided to make separate classes for each car. With rendering logic of each car baked into the class itself.
When Rajesh saw Mohan’s code, he laughed at the code duplication in different classes. “There is no way Rahul is going to approve this”, he thought to himself and smiled.
The next day, Rahul called both the developers to his office, and Rajesh could almost smell the salary hike coming his way but...
Rahul: ‘Guys, there is a change of plans! our new product designer thinks that it would be way more cool if our cars can play sound as well.’
Disappointed, Rajesh ran towards his cubicle to complete this before Mohan. He quickly added another method, playSound().
He tested the changes, submitted his code, and started dreaming about his trip to Thailand after getting promoted. After a few days, Rahul once again called both into his office.
Rahul: ‘Good work guys, I am really impressed with both of you. But after careful consideration, we have decided to promote Mohan to senior dev position.’
Rajesh: ‘What? Didn’t Mohan have duplicate code in his implementation! How is he getting promoted, not me ?’
Do you also want to know why Mohan was promoted? But before that, you need to know about Mohan’s encounter with the ‘4 core principles, also known as 4 pillars of Object-Oriented Programming.
'The quality of dealing with ideas rather than events.'
Abstraction refers to the ability of an object to expose higher-level functionalities while hiding the implementation details from the users.
For example, when you think about a car, you don’t think about the thousand parts that make up a car. You probably think about a well-defined machine on 4 wheels which can take you from one place to another. A Car can be a Ferrari or Tesla or a BumbleBee, but still, it’s a ‘Car’. In other words, we can say that although the ‘implementation’ of cars varies, abstraction ‘Car’ remains the same.
‘Abstraction allows you to program to an interface, and hide the implementation details’.
There are many ways to implement abstraction, but the most common ones in Java are abstractions through interfaces and abstraction through classes.
Abstraction through Classes: This is achieved using the ‘abstract’ keyword with a class. In Java, when you mark a class as ‘abstract’, that implies that it cannot be instantiated.
Mohan: ‘But what’s the point of creating a class which cannot be instantiated?’
“The point is we are using an abstract class to define a ‘contract’. A contract about what things the class must do, not how those things are done. To utilize the power of abstract classes, think about extension instead of initialization.”— The wise developer replied
Abstraction via Interfaces: Similar to abstract classes, we can use the ‘interface’ keyword to define an interface.
Notice that both the implementations look exactly the same. Hence It is natural to ask why we have two different keywords for similar functionalities.
The main reason interfaces exist is to allow an object to implement multiple abstractions. As Java does not allow a class to extend more than one class, due to known problems with multiple inheritances.
There are few other differences between abstraction and interface, for example
The action of enclosing something in or as if in a capsule.
If you have ever been sick and had to take a capsule, the contents of which you never knew, then you already know what encapsulation is!
Formally, Encapsulation is the ability of a system to hide information in such a way that it cannot be accessed or manipulated directly by other entities.
Mohan: ‘But why would someone want that? If I hide something inside an object, what’s the point of creating that attribute in the first place ?’
‘Encapsulation allows us to have better control and avoid unpredictable state changes to your system. Say in the previous example of the car, had we directly exposed our engine object, don’t you think anyone can directly call engine.ignite() without the keys ?’ — The wise developer replied
Mohan: Yeah that's true. So how do we use encapsulation in our system?
The answer is: Using Access Modifiers.
Access modifiers in Java specify the accessibility or scope of a field, method, constructor, or class.
The general idea of this mechanism is simple. For any attribute of your class, mark it with a ‘private or protected’ access modifier. And provide getters and setters method with ‘public’ access modifier. This way, your object can have complete control over how its data will change.
Food for Thought : Is class an encapsulation or abstraction ?
We just encountered two very similar concepts, Encapsulation and Abstraction. Notice that the idea of creating a class can be considered as an encapsulation, because we are ‘encapsulating’ data and operations together. Or we can call it an Abstraction, as we are also exposing an interface in this process for performing manipulations on that object.
As it happens most of the time in life, both are true and the ‘difference’ lies in the intent.
With Abstraction, the intent is ‘Simplification’.
With Encapsulation, the intent is ‘Control’.
Let that sink in a little bit.
When do we use ‘abstraction’ in our daily life? When we want to project a simplified view of our ever-evolving system. For example, By using the abstraction ‘Car’, we can collectively refer to the shared properties of millions of different cars in this world. Although each car can have its own unique traits, abstraction allows us to focus on the general properties of a car instead.
Similarly, Having no access boundaries means that your class or system is completely ‘open for modification’ and it’s very easy to make changes to your system which can cause undesirable side effects. Hence we add ‘Encapsulation’ to our system for ‘better control’.
To derive (a quality, characteristic, or predisposition) genetically from one’s parents or ancestors.
Inheritance refers to a mechanism that allows an object to reuse or extend the functionality of another object.
Rahul: ‘Rajesh, Do you want to know why I promoted Mohan ?’
Rajesh. : ‘Yes’
Rahul: ‘Perhaps you should take a look at the final design that Mohan submitted.’
Rajesh: ‘How did you come up with this ?’
Mohan: ‘Well, I had realized during my first review that code duplicity is not going to work. So I encapsulated the common properties into a separate class Car and then I used inheritance to share those common properties with all the concrete classes.’
When you design with inheritance, you put common code in a class and then tell other more specific classes that the common (more abstract) class is their superclass. That way, the subclass inherits the members of the superclass and it can use them in a similar way to how it uses its own members. This approach allows you to write code that is easier to reuse and extend.
Rajesh: ‘I still don’t get it. How are you going to render different car models using this? I don’t see any conditional checks for rendering different cars.’
Mohan: ‘Actually, we don’t need that anymore. The Java Virtual Machine, aka JVM, takes care of invoking the correct render() and playSound() methods based on the instance type. And this mechanism is popularly known as…’
“The condition of occurring in several different forms.”
Polymorphism in OOP refers to the ability of an object to behave differently according to the ‘context’ of invocation. For example, Java allows you to create multiple implementations of a method with separate arguments. This means that the method will behave differently based on the number and type of arguments provided. This is known as static polymorphism, as the decision for invoking the correct function is taken ‘statically’ at compile time.
But the more interesting form of polymorphism is ‘runtime polymorphism’, Where the decision of method invocation is taken at ‘runtime’. Like Mohan’s code, where each of the concrete classes provides an implementation of the render() method. This is popularly known as ‘method overriding’. As we are over-writing the base class’s behavior.
Notice that how we are calling render() on the parent class variable, but our JVM magically figures out the correct method to call based on the object instance.
Mohan: 'As our code is changing behaviours (aka methods) at runtime according to its ‘context of invocation’ (aka object instance). We call it poly-morph-ism (aka that which has multiple forms).’
Rahul: Having an understanding of fundamental principles of Object-Oriented-Programming is crucial as you move ahead in your career. Not only does it saves time and helps you in adapting to the changing requirements of a business, but very often it can have a direct revenue impact as well. Hope that answers your question, Rajesh.
Do you want to know what happened to Rajesh after this ? was he able to get the promotion in the next perf cycle? If yes, do let me know in the comments!
Subscribe to get weekly content on data structure and algorithms, machine learning, system design and oops.