The 4 Pillars of Object-Oriented Programming
As I have progressed through my software development journey, applied to jobs, and completed technical interviews, one question seems to pop up fairly often: what are the four pillars of object-oriented programming?
A quick google search will reveal a plethora of articles and blog posts about this topic. However, when I first began studying the four pillars, I actually found it difficult to find clear, concise definitions for each concept. The definitions I read seemed very broad and vague, and I found it hard to actually internalize and fully understand the material I was reading. When asked about the pillars during interviews, I struggled to find a good explanation in my own words for the core concepts that define each pillar.
In this blog, I’ll try to break down the four pillars in simple terms, based on specific examples, to hopefully help those aspiring developers out there who might also be struggling to wrap their heads around this basic, yet elusive topic.
Encapsulation is a mechanism of wrapping the data (variables) and code acting on the data (methods) together as a single unit. In encapsulation, the variables of a class will be hidden from other classes, and can be accessed only through the methods of their current class. Therefore, it is also known as data hiding.
Encapsulation is accomplished when each object maintains a private state, inside a class. Other objects can not access this state directly, instead, they can only invoke a list of public functions.
In the example above, the private variables energy and hunger are only directly accessible within the Dog class. However, within the Dog class, you can implement “getter” and “setter” methods, such as getHunger() and setHunger(). These are public methods, which means that outside classes can invoke these methods to manipulate the energy and hunger variables. Thus, encapsulation allows developers to specify exactly which data should be allowed to be changed by a user, and which data should remain constant and hidden.
Inheritance is the ability of one object to acquire some or all properties of another object. The classic paradigm for understanding inheritance is that of a parent and child. Children inherit traits from their parents, but can also develop or display their own unique traits. This is similar to the functionality of parent and child classes.
Inheritance can be very useful for reducing the amount of code a developer must write to accomplish certain tasks. By building a hierarchy of classes that share large amounts of methods and data, not only does your code become more “DRY”, but the code also conserves a single source of truth within the higher-level, parent classes. Changes that occur within these classes will then immediately cascade down the parent-child chain.
Inheritance is implemented fairly simply. Just use the extends keyword when defining a child class, and make sure that both the parent and child files are located in the same directory. See below.
Abstraction is an extension of encapsulation, in that it is also concerned with what information is broadly accessible to the user, and what information is hidden. My preferred real-world analogy for understanding abstraction is driving a car.
In order to operate a vehicle, you need to learn a variety of skills. You need to learn how to operate the steering, brakes, gas pedal, how to park the car, drive on the highway, etc. However, you do not really need to know anything about the how the car’s engine actually functions to properly use it to its fullest extent. While it might be helpful to have some basic knowledge about car maintenance and mechanics, all you need to know is how to drive the car.
This is the essence of abstraction. Abstraction is the process of hiding the implementation details of your code from the person using it. All the user needs to know is how to utilize the functionality that your code provides. Using abstraction is beneficial because it adds a layer of security. It’s generally a good idea to prevent the user from having direct access to the inner workings of your program. Additionally, it provides better functionality. Abstraction ensures that the user is not overloaded with a ton of “under-the-hood” information that they don’t really need to know about to use the application.
Abstraction is accomplished with abstract classes. An abstract class is a restricted class that cannot be used to create objects (to access it, it must be inherited from another class).
In the above example, the abstract class Animal cannot actually be instantiated. Rather, it is used as a parent class for the Pig class. In this way, the abstract Animal class is used as a storage facility for all of the data and methods that we want to hide from the user. Other public classes can then inherit and use these methods without having to explicitly define them.
Polymorphism, in my opinion, is the most difficult pillar to understand fully. Essentially, polymorphism is a feature of object-oriented programming languages, that allows a single piece of information, such as a class or method, to accomplish multiple tasks, depending upon the circumstances in which it is invoked.
Think about it like this. People are perfect examples of polymorphic behavior. A person existing in one moment can have multiple characteristics. For example, a man can be simultaneously a father, a husband, an employee, etc. So the same person possesses different behavior in different situations. This is called polymorphism.
The example below displays a very simple example of polymorphism in Java. A parent class is defined with a Print() method that is extended to two different subclasses. Each of these three classes defines the exact same method, but with different print statements.
Next, we define a fourth executable class which instantiates a member of the Parent class with the variable a, but then sets this instance equal to each of the subclasses. When the code is run, the Print() method of the Parent class is actually overwritten, in favor of the subclass Print() methods, creating the following output. Thus, the Print() method in this case exhibits different behavior depending upon how it is invoked in the main method.
And there you have it! Hopefully this article has shed some light on a basic, yet surprisingly complex subject. The four pillars are foundational for anyone interested in object-oriented programming, and this tutorial only really scratches the surface of what they can help you accomplish in your coding. I would highly suggest checking out some of the resources I’ve listed below, for more information.
The Four Pillars of Object Oriented Programming
In this article, I will try to explain the main four principles of Object-Oriented-Programming (OOP)…
Java - Polymorphism
Polymorphism is the ability of an object to take on many forms. The most common use of polymorphism in OOP occurs when…