Core OOP Principles in Practice
Revisit the four pillars of OOP—Abstraction, Encapsulation, Inheritance, and Polymorphism—and learn how to apply them to build modular systems.
Learning Goals
- Apply Abstraction and Encapsulation to protect system integrity.
- Evaluate the trade-offs between Inheritance and Composition.
- Implement Polymorphic behavior to handle varying requirements gracefully.
Core OOP Principles in Practice
Object-Oriented Programming (OOP) is the bedrock of Low Level Design. While many developers are familiar with the definitions of the "Four Pillars," applying them effectively in a real-world system requires a deeper understanding of their practical implications.
The Four Pillars of OOP
1. Abstraction
Abstraction is about focusing on what an object does rather than how it does it. In LLD, we use interfaces and abstract classes to define contracts.
- Practical Tip: Design your modules so that they depend on interfaces, not concrete implementations. This makes it trivial to swap out logic later.
2. Encapsulation
Encapsulation is the practice of bundling data (attributes) and methods that operate on that data within a single unit (class), and restricting direct access to some of the object's components.
- Practical Tip: Keep your fields
private. Use getters/setters (or properties) only when necessary, and ensure that the object maintains its own internal state integrity.
3. Inheritance
Inheritance allows a class to acquire properties and behaviors from another class. While powerful, it is often overused.
- Practical Tip: Favor Composition over Inheritance. Inheritance creates a "is-a" relationship, which is often too rigid. Use it only when there is a true hierarchical relationship.
4. Polymorphism
Polymorphism allows objects of different types to be treated as objects of a common base type.
- Practical Tip: Leverage method overriding and interfaces to allow your system to handle new types of objects without changing the core processing logic.
Refactoring: From Procedural to Object-Oriented
- 1Step 1
Suppose we have a script that calculates areas for different shapes. In a procedural approach, you might have a single function with a giant
switchstatement that checks atypestring and performs math. - 2Step 2
Define a common interface or abstract class. In this case, an
IShapeinterface with acalculateArea()method. This establishes the contract that all shapes must fulfill. - 3Step 3
Create specific classes like
Circle,Rectangle, andSquare. Each class encapsulates its own data (e.g.,radiusfor Circle) and provides its own implementation ofcalculateArea(). - 4Step 4
Instead of a switch statement, your main logic now takes a list of
IShapeobjects and callscalculateArea()on each. The system doesn't need to know the specific type; it just trusts the interface.
Example: A Payment Processing System
Let's look at how these pillars come together in a Payment System design.
In this design:
- Abstraction:
IPaymentProcessorhides the complexity of different vendors. - Encapsulation:
CreditCardProcessorkeeps itsapiKeyprivate. - Polymorphism:
PaymentServicecan work with any processor that implements the interface. - Composition:
PaymentServicehas-a processor, it doesn't inherit from one.
Knowledge Check
Which OOP pillar is most directly violated when a class allows external code to directly modify its private internal state?