OOP
Object-Oriented Programming (OOP) is a programming paradigm (way of writing code) that uses objects and classes to model real-world entities.
It’s widely adopted in modern software development due to its ability to create clean, reusable, and scalable code.
Learning OOP allows us to structure code in a way that mirrors real-world systems, making it easier to design and maintain complex applications.
Benefits of OOP
- Modularity: Organizes code into self-contained units (objects) for easier understanding and maintenance.
- Reusability: Once a class is written, it can be reused in different parts of the program or across projects.
- Scalability: New features and objects can be easily added as the codebase grows.
- Maintainability: Changes are easier to manage, and debugging becomes more efficient.
- Real-world Modeling: Models real-world entities, making the code more intuitive and relatable.
- Parallel Development: Enables team members to work on different objects or classes simultaneously.
In this article, I'll break down core OOP concepts with easy-to-understand code examples.
1. Classes & Objects
A class is like a blueprint or a recipe for creating objects. It defines what data and behavior the objects will have. An object is an instance of a class, created using the class definition.
🧠 Think of a class as a car design, and an object as an actual car made using that design.
Here's an example of a class
definition in C++:
class Car {
public:
string brand;
int year;
void displayInfo() {
cout << "Brand: " << brand << ", Year: " << year << endl;
}
};
In this example, Car
is a class - a blueprint that defines the properties and behavior of the class.
- The class itself doesn’t hold any real data. It just outlines what an object of this type will look like and how it will behave.
Car toyotaCar;
toyotaCar.brand = "Toyota";
toyotaCar.year = 2023;
toyotaCar.displayInfo();
Car hondaCar;
hondaCar.brand = "Honda";
hondaCar.year = 2022;
hondaCar.displayInfo();
Here, toyotaCar
and hondaCar
are objects or instances of the Car
class. They hold specific data and can invoke the same behavior. This demonstrates how OOP allows for reusable, modular code.
- An object turns the blueprint into something real - it holds actual data and can perform actions.
2. Encapsulation
Encapsulation is the process of bundling data and methods that operate on that data within a class, while restricting direct access to some of the object's components. It ensures that data is accessed only through defined interfaces, protecting it from external interference and misuse.
In simple terms, encapsulation allows us to hide the internal state of an object and expose only what is necessary for its operation.
In C++, we can achieve encapsulation using access specifiers.
class BankAccount {
private:
double balance;
public:
void deposit(double amount) {
if (amount > 0) balance += amount;
}
void displayBalance() {
cout << "Balance: $" << balance << endl;
}
};
int main() {
BankAccount account;
account.deposit(500);
account.displayBalance();
}
Here, the BankAccount
class represents a bank account with a private data member balance
. This means that the balance
cannot be accessed or modified directly from outside the class.
- We interact with the balance using public methods like
deposit
anddisplayBalance
.
3. Inheritance
Inheritance allows one class (child class) to inherit features (methods and properties) from another class (parent class).
The class that inherits is called the child class (or derived class), and the class being inherited from is called the parent class.
The child class inherits all the fields and methods of the parent class and can also add new fields and methods or override the ones inherited from the parent class.
This enables code reuse, makes the codebase DRY (Don't Repeat Yourself), and supports hierarchical classification.
Let’s say we have a parent Animal
class with a method named speak()
.
class Animal {
public:
void speak() {
cout << "Animal speaks" << endl;
}
};
class Dog : public Animal {
public:
void bark() {
cout << "Dog barks" << endl;
}
};
int main() {
Dog myDog;
myDog.speak(); // Inherited from Animal
myDog.bark(); // Defined in Dog
}
Here, the Dog
class inherits from the Animal
class, which means it can access the speak()
method or any other attribute or method defined publicly or protected in the Animal
class. The Dog
class also adds its own attributes and methods, such as bark()
.
- This demonstrates inheritance, where a subclass inherits properties and behaviors from its parent class while adding its own unique functionality.
4. Polymorphism
Polymorphism refers to the ability of a single method to behave differently depending on the object that invokes it.
It allows you to write flexible and reusable code that can work with objects of different types, as long as they implement a shared interface.
This behavior is commonly achieved through method overriding in inheritance.
Method overriding occurs when a subclass provides its own implementation of a method that is already defined in its parent class.
For instance, let’s consider an interface Animal
with a method sound()
.
class Animal {
public:
virtual void sound() {
cout << "Animal makes a sound" << endl;
}
};
class Cat : public Animal {
public:
void sound() override {
cout << "Cat meows" << endl;
}
};
int main() {
Animal* animal = new Cat();
animal->sound(); // Outputs "Cat meows" due to polymorphism
delete animal;
}
Here, the Animal
class defines a virtual function sound()
, which can be overridden by derived classes. Each subclass (such as Cat
) of Animal
implements the sound
method differently (method overriding), but the interface remains consistent, allowing iteration over both classes using a single pointer.
- This demonstrates polymorphism, where the method called depends on the actual object type, not the pointer type, enabling dynamic method binding at runtime.
5. Abstraction
Abstraction is the concept of hiding unnecessary details and showing only the relevant information. It's an extension of encapsulation, focusing more on the interface and less on the implementation.
Abstraction helps to simplify complex systems and focus on the essential features.
In C++, we can implement abstraction using abstract classes and pure virtual functions.
Let’s say we have an abstract base class called Shape
. The Shape
class is marked as an abstract class by inheriting from the ABC class (Abstract Base Class).
Inside the Shape
class, we define an abstract method called draw()
.
class Shape {
public:
virtual void draw() = 0; // Pure virtual function (abstract method)
};
class Circle : public Shape {
public:
void draw() override {
cout << "Drawing a Circle" << endl;
}
};
int main() {
Shape* shape = new Circle();
shape->draw();
delete shape;
}
Here, the Circle
class inherits from the Shape
class.
It provides its own implementation of the draw()
method specific to itself. Note that the implementation details are hidden from the outside world, and only the interface defined by the abstract class is exposed.
- This demonstrates how Abstraction hides the implementation details (like how a circle is drawn) and only exposes the method
draw()
that can be called on anyShape
object.
These are the fundamental principles of Object-Oriented Programming.
Implementing these concepts will help you write cleaner, reusable, and maintainable code.
💬 Did you find this useful?
If this article helped you, feel free to like and share!
Have any questions or ideas?
Leave a comment below — I'd love to hear from you! 🙌
Top comments (0)