OOP Relationships in C++: IS-A vs HAS-A (Inheritance vs Composition)
This comprehensive guide explains the two most important object-oriented relationships:
- IS-A relationship → Inheritance
- HAS-A relationship → Composition
Understanding this is critical for:
- C++ OOP
- Java OOP
- System design
- Backend architecture
- LLD interviews
Table of Contents
- IS-A Relationship (Inheritance)
- HAS-A Relationship (Composition)
- Composition vs Aggregation
- Key Differences
- Design Principles
- Real-World Examples
- Common Mistakes
- Interview Questions
1. IS-A Relationship (Inheritance)
Definition
An IS-A relationship means a child class is a specialized type of parent class. The child class inherits properties and behaviors from the parent class.
Litmus Test: If this sentence makes grammatical and logical sense:
"Child is a Parent"
then inheritance is the correct choice.
Examples
| Child Class | Parent Class | Sentence Test |
|---|---|---|
| Dog | Animal | "Dog is an Animal" ✅ |
| Car | Vehicle | "Car is a Vehicle" ✅ |
| Circle | Shape | "Circle is a Shape" ✅ |
| Manager | Employee | "Manager is an Employee" ✅ |
| IndianTax | TaxCalculator | "IndianTax is a TaxCalculator" ✅ |
Basic Syntax (C++)
#include <iostream>
using namespace std;
class Parent {
public:
void show() {
cout << "Parent function" << endl;
}
};
class Child : public Parent {
public:
void display() {
cout << "Child function" << endl;
}
};
int main() {
Child c;
c.show(); // Inherited from Parent
c.display(); // Child's own method
return 0;
}
Output:
Parent function
Child function
Note: Child inherits all public/protected members of Parent.
Types of Inheritance
1. Single Inheritance
One child inherits from one parent.
class Animal {
public:
void eat() { cout << "Eating..." << endl; }
};
class Dog : public Animal {
public:
void bark() { cout << "Barking..." << endl; }
};
2. Multiple Inheritance
One child inherits from multiple parents.
class Flyable {
public:
void fly() { cout << "Flying..." << endl; }
};
class Swimmable {
public:
void swim() { cout << "Swimming..." << endl; }
};
class Duck : public Flyable, public Swimmable {
public:
void quack() { cout << "Quacking..." << endl; }
};
3. Multilevel Inheritance
Chain of inheritance (Grandparent → Parent → Child).
class Animal {
public:
void eat() { cout << "Eating..." << endl; }
};
class Mammal : public Animal {
public:
void breathe() { cout << "Breathing..." << endl; }
};
class Dog : public Mammal {
public:
void bark() { cout << "Barking..." << endl; }
};
4. Hierarchical Inheritance
Multiple children inherit from one parent.
class Shape {
public:
virtual double area() = 0;
};
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
double area() override { return 3.14159 * radius * radius; }
};
class Rectangle : public Shape {
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() override { return width * height; }
};
Why Use Inheritance?
1. Code Reuse
Child automatically gets parent properties and methods without rewriting code.
class Vehicle {
protected:
string brand;
int speed;
public:
void start() { cout << "Vehicle started" << endl; }
void stop() { cout << "Vehicle stopped" << endl; }
};
class Car : public Vehicle {
int numDoors;
public:
// Car automatically has start(), stop(), brand, speed
void honk() { cout << "Beep beep!" << endl; }
};
class Motorcycle : public Vehicle {
bool hasSidecar;
public:
// Motorcycle also has start(), stop(), brand, speed
void wheelie() { cout << "Doing a wheelie!" << endl; }
};
2. Runtime Polymorphism
Allows different behavior using the same interface through virtual functions.
class Animal {
public:
virtual void sound() = 0; // Pure virtual function
virtual ~Animal() {} // Virtual destructor
};
class Dog : public Animal {
public:
void sound() override {
cout << "Dog barks: Woof!" << endl;
}
};
class Cat : public Animal {
public:
void sound() override {
cout << "Cat meows: Meow!" << endl;
}
};
class Cow : public Animal {
public:
void sound() override {
cout << "Cow moos: Moo!" << endl;
}
};
// Usage - Polymorphism in action
int main() {
Animal* animals[3];
animals[0] = new Dog();
animals[1] = new Cat();
animals[2] = new Cow();
// Same function call → different behavior
for (int i = 0; i < 3; i++) {
animals[i]->sound();
}
// Cleanup
for (int i = 0; i < 3; i++) {
delete animals[i];
}
return 0;
}
Output:
Dog barks: Woof!
Cat meows: Meow!
Cow moos: Moo!
3. Extensibility
Easy to add new types without modifying existing code (Open/Closed Principle).
// Adding a new animal is easy - just create a new class
class Lion : public Animal {
public:
void sound() override {
cout << "Lion roars: Roar!" << endl;
}
};
// No changes needed to existing code!
Inheritance Access Specifiers
class Base {
public:
int publicVar;
protected:
int protectedVar;
private:
int privateVar;
};
| Access Specifier | public members | protected members | private members |
|---|---|---|---|
public inheritance |
public in derived | protected in derived | not accessible |
protected inheritance |
protected in derived | protected in derived | not accessible |
private inheritance |
private in derived | private in derived | not accessible |
Access Comparison Table
| Access Type | External Code | Derived Class |
|---|---|---|
public |
✅ Accessible | ✅ Accessible |
protected |
❌ Not accessible | ✅ Accessible |
private |
❌ Not accessible | ❌ Not accessible |
When to Use Inheritance
Correct Usage ✅
| Statement | Why It Works |
|---|---|
| Dog IS-A Animal | Logical type hierarchy |
| Manager IS-A Employee | Manager is specialized Employee |
| SavingsAccount IS-A BankAccount | Specialized account type |
| ElectricCar IS-A Car | Specialized car type |
Wrong Usage ❌
| Statement | Why It's Wrong |
|---|---|
| Car IS-A Engine | Car contains engine, not a type of engine |
| Invoice IS-A TaxCalculator | Invoice uses tax calculator |
| House IS-A Room | House contains rooms |
| Computer IS-A CPU | Computer contains CPU |
2. HAS-A Relationship (Composition)
Definition
A HAS-A relationship exists when a class contains or uses another class as a member. The containing class is composed of other objects.
Litmus Test: If this sentence makes sense:
"Class A has a Class B"
then composition is the correct choice.
Examples
| Container Class | Contained Class | Sentence Test |
|---|---|---|
| Car | Engine | "Car has an Engine" ✅ |
| Computer | CPU | "Computer has a CPU" ✅ |
| House | Room | "House has Rooms" ✅ |
| Order | Payment | "Order has a Payment" ✅ |
| Person | Address | "Person has an Address" ✅ |
Basic Syntax
Object Composition (Strong Ownership)
class Engine {
public:
void start() {
cout << "Engine started" << endl;
}
void stop() {
cout << "Engine stopped" << endl;
}
};
class Car {
private:
Engine engine; // HAS-A relationship (composition)
string model;
public:
Car(string m) : model(m) {}
void startCar() {
cout << "Starting " << model << "..." << endl;
engine.start();
}
void stopCar() {
engine.stop();
cout << model << " stopped" << endl;
}
};
int main() {
Car myCar("Tesla Model 3");
myCar.startCar();
myCar.stopCar();
return 0;
}
Output:
Starting Tesla Model 3...
Engine started
Engine stopped
Tesla Model 3 stopped
Pointer Composition (Dynamic/Flexible)
class Engine {
int horsepower;
public:
Engine(int hp) : horsepower(hp) {}
void start() { cout << "Engine (" << horsepower << " HP) started" << endl; }
int getPower() { return horsepower; }
};
class Car {
private:
Engine* engine; // Pointer-based composition
string model;
public:
Car(string m, int hp) : model(m) {
engine = new Engine(hp);
}
~Car() {
delete engine; // Clean up owned resource
}
void start() {
cout << "Starting " << model << endl;
engine->start();
}
};
Modern C++ with Smart Pointers (Recommended)
#include <memory>
class Engine {
public:
void start() { cout << "Engine started" << endl; }
};
class Car {
private:
unique_ptr<Engine> engine; // Automatic memory management
string model;
public:
Car(string m) : model(m), engine(make_unique<Engine>()) {}
void start() {
cout << "Starting " << model << endl;
engine->start();
}
// No destructor needed - smart pointer handles cleanup
};
Composition with Dependency Injection
This is the preferred approach in professional software development.
// Interface (Abstract class)
class IEngine {
public:
virtual void start() = 0;
virtual void stop() = 0;
virtual ~IEngine() {}
};
// Concrete implementations
class PetrolEngine : public IEngine {
public:
void start() override { cout << "Petrol engine: Vroom!" << endl; }
void stop() override { cout << "Petrol engine stopped" << endl; }
};
class ElectricEngine : public IEngine {
public:
void start() override { cout << "Electric engine: Whirr..." << endl; }
void stop() override { cout << "Electric engine stopped" << endl; }
};
// Car depends on interface, not concrete implementation
class Car {
private:
IEngine* engine; // Dependency injection
string model;
public:
// Engine is injected from outside
Car(string m, IEngine* e) : model(m), engine(e) {}
void start() {
cout << "Starting " << model << endl;
engine->start();
}
void stop() {
engine->stop();
}
};
int main() {
PetrolEngine petrol;
ElectricEngine electric;
Car gasCar("Honda Civic", &petrol);
Car evCar("Tesla Model 3", &electric);
gasCar.start();
evCar.start();
return 0;
}
Output:
Starting Honda Civic
Petrol engine: Vroom!
Starting Tesla Model 3
Electric engine: Whirr...
3. Composition vs Aggregation
Both are HAS-A relationships but differ in ownership semantics.
Composition (Strong Ownership)
- Lifetime dependency: Part cannot exist without the whole
- Ownership: Container owns and manages the part's lifecycle
- UML notation: Filled diamond (◆)
class Heart {
public:
void beat() { cout << "Heart is beating" << endl; }
};
class Human {
private:
Heart heart; // Composition - Heart is created and destroyed with Human
public:
void live() {
heart.beat();
}
};
// When Human object is destroyed, Heart is automatically destroyed
Aggregation (Weak Ownership)
- Independent lifecycle: Part can exist independently of the whole
- Ownership: Container uses but doesn't own the part
- UML notation: Empty diamond (◇)
class Professor {
string name;
public:
Professor(string n) : name(n) {}
string getName() { return name; }
};
class Department {
private:
vector<Professor*> professors; // Aggregation - Professors exist independently
public:
void addProfessor(Professor* p) {
professors.push_back(p);
}
void listProfessors() {
for (auto p : professors) {
cout << p->getName() << endl;
}
}
};
int main() {
Professor* john = new Professor("Dr. John");
Professor* jane = new Professor("Dr. Jane");
Department csDept;
csDept.addProfessor(john);
csDept.addProfessor(jane);
// Professors can exist even if Department is destroyed
Department mathDept;
mathDept.addProfessor(john); // Same professor in multiple departments
delete john;
delete jane;
return 0;
}
Comparison Table
| Aspect | Composition | Aggregation |
|---|---|---|
| Ownership | Strong (owns the part) | Weak (uses the part) |
| Lifecycle | Part dies with whole | Part survives whole |
| Multiplicity | Part belongs to one whole | Part can belong to many |
| Example | Human-Heart | Department-Professor |
| UML Symbol | ◆ (filled diamond) | ◇ (empty diamond) |
4. Key Differences
IS-A vs HAS-A Comparison
| Feature | Inheritance (IS-A) | Composition (HAS-A) |
|---|---|---|
| Relationship | "is a type of" | "has a" / "uses a" |
| Purpose | Type hierarchy & polymorphism | Reuse functionality |
| Coupling | Tight coupling | Loose coupling |
| Flexibility | Less flexible | Highly flexible |
| Runtime Changes | Cannot change parent | Can swap components |
| Testing | Harder to mock | Easy to mock |
| Code Reuse | Through inheritance | Through delegation |
| Industry Preference | Use sparingly | Highly preferred |
Visual Comparison
INHERITANCE (IS-A) COMPOSITION (HAS-A)
================== ===================
Animal Car
↑ / \
| Engine Wheel
Dog
Car "has" Engine
Dog "is a" Animal Car "has" Wheels
5. Design Principles
Golden Rule (Industry Standard)
"Prefer composition over inheritance"
— Gang of Four (Design Patterns Book)
Why Composition is Preferred
- Loose Coupling: Components can be changed independently
- Flexibility: Behavior can be changed at runtime
- Testability: Easy to mock dependencies
- Single Responsibility: Each class has one reason to change
- Avoids Fragile Base Class: Changes to parent don't break children
When to Use Inheritance
Use inheritance only when:
- There is a genuine IS-A relationship
- You need runtime polymorphism
- The relationship is unlikely to change
- Liskov Substitution Principle (LSP) is satisfied
Liskov Substitution Principle (LSP)
Objects of a superclass should be replaceable with objects of subclasses without breaking the application.
// Good Example - LSP satisfied
class Bird {
public:
virtual void move() { cout << "Bird is moving" << endl; }
};
class Sparrow : public Bird {
public:
void move() override { cout << "Sparrow is flying" << endl; }
};
class Penguin : public Bird {
public:
void move() override { cout << "Penguin is swimming" << endl; }
};
// Bad Example - LSP violated
class Bird {
public:
virtual void fly() { cout << "Bird is flying" << endl; }
};
class Penguin : public Bird { // Penguin can't fly!
public:
void fly() override {
throw runtime_error("Penguins can't fly!"); // Violates LSP
}
};
6. Real-World Examples
Example 1: E-Commerce System
// Interfaces
class ITaxCalculator {
public:
virtual double calculateTax(double amount) = 0;
virtual ~ITaxCalculator() {}
};
class IPaymentProcessor {
public:
virtual bool processPayment(double amount) = 0;
virtual ~IPaymentProcessor() {}
};
// Implementations (IS-A relationships)
class IndianTax : public ITaxCalculator {
public:
double calculateTax(double amount) override {
return amount * 0.18; // 18% GST
}
};
class USTax : public ITaxCalculator {
public:
double calculateTax(double amount) override {
return amount * 0.08; // 8% Sales tax
}
};
class CreditCardProcessor : public IPaymentProcessor {
public:
bool processPayment(double amount) override {
cout << "Processing credit card payment: $" << amount << endl;
return true;
}
};
class PayPalProcessor : public IPaymentProcessor {
public:
bool processPayment(double amount) override {
cout << "Processing PayPal payment: $" << amount << endl;
return true;
}
};
// Order class using HAS-A relationships
class Order {
private:
ITaxCalculator* taxCalculator; // HAS-A
IPaymentProcessor* paymentProcessor; // HAS-A
double subtotal;
public:
Order(ITaxCalculator* tax, IPaymentProcessor* payment)
: taxCalculator(tax), paymentProcessor(payment), subtotal(0) {}
void addItem(double price) {
subtotal += price;
}
double getTotal() {
return subtotal + taxCalculator->calculateTax(subtotal);
}
bool checkout() {
double total = getTotal();
cout << "Subtotal: $" << subtotal << endl;
cout << "Tax: $" << taxCalculator->calculateTax(subtotal) << endl;
cout << "Total: $" << total << endl;
return paymentProcessor->processPayment(total);
}
};
int main() {
IndianTax indianTax;
CreditCardProcessor creditCard;
Order order(&indianTax, &creditCard);
order.addItem(100);
order.addItem(50);
order.checkout();
return 0;
}
Relationships:
-
IndianTaxIS-AITaxCalculator(Inheritance) -
CreditCardProcessorIS-AIPaymentProcessor(Inheritance) -
OrderHAS-AITaxCalculator(Composition) -
OrderHAS-AIPaymentProcessor(Composition)
Example 2: Game Character System
// Component interfaces
class IWeapon {
public:
virtual void attack() = 0;
virtual int getDamage() = 0;
virtual ~IWeapon() {}
};
class IArmor {
public:
virtual int getDefense() = 0;
virtual ~IArmor() {}
};
// Weapon implementations (IS-A)
class Sword : public IWeapon {
public:
void attack() override { cout << "Sword slash!" << endl; }
int getDamage() override { return 25; }
};
class Bow : public IWeapon {
public:
void attack() override { cout << "Arrow shot!" << endl; }
int getDamage() override { return 15; }
};
class Staff : public IWeapon {
public:
void attack() override { cout << "Magic blast!" << endl; }
int getDamage() override { return 30; }
};
// Armor implementations (IS-A)
class PlateArmor : public IArmor {
public:
int getDefense() override { return 50; }
};
class LeatherArmor : public IArmor {
public:
int getDefense() override { return 20; }
};
// Character base class
class Character {
protected:
string name;
int health;
IWeapon* weapon; // HAS-A
IArmor* armor; // HAS-A
public:
Character(string n, int h) : name(n), health(h), weapon(nullptr), armor(nullptr) {}
void equip(IWeapon* w) { weapon = w; }
void equip(IArmor* a) { armor = a; }
virtual void attack() {
if (weapon) {
cout << name << " attacks! ";
weapon->attack();
}
}
void showStats() {
cout << "=== " << name << " ===" << endl;
cout << "Health: " << health << endl;
cout << "Damage: " << (weapon ? weapon->getDamage() : 0) << endl;
cout << "Defense: " << (armor ? armor->getDefense() : 0) << endl;
}
virtual ~Character() {}
};
// Character types (IS-A)
class Warrior : public Character {
public:
Warrior(string n) : Character(n, 150) {}
void rage() {
cout << name << " enters rage mode!" << endl;
}
};
class Mage : public Character {
public:
Mage(string n) : Character(n, 80) {}
void castSpell() {
cout << name << " casts a powerful spell!" << endl;
}
};
int main() {
// Create weapons and armor
Sword sword;
Staff staff;
PlateArmor plate;
LeatherArmor leather;
// Create characters
Warrior thor("Thor");
Mage gandalf("Gandalf");
// Equip characters (composition at work)
thor.equip(&sword);
thor.equip(&plate);
gandalf.equip(&staff);
gandalf.equip(&leather);
// Show stats
thor.showStats();
cout << endl;
gandalf.showStats();
cout << endl;
thor.attack();
gandalf.attack();
return 0;
}
Example 3: Logger System
#include <fstream>
#include <ctime>
// Logger interface
class ILogger {
public:
virtual void log(string message) = 0;
virtual ~ILogger() {}
};
// Logger implementations (IS-A)
class ConsoleLogger : public ILogger {
public:
void log(string message) override {
cout << "[CONSOLE] " << message << endl;
}
};
class FileLogger : public ILogger {
string filename;
public:
FileLogger(string file) : filename(file) {}
void log(string message) override {
ofstream file(filename, ios::app);
file << "[FILE] " << message << endl;
file.close();
}
};
// Application using composition
class Application {
private:
string name;
ILogger* logger; // HAS-A relationship
public:
Application(string n, ILogger* l) : name(n), logger(l) {}
void run() {
logger->log(name + " started");
// Application logic here...
logger->log(name + " completed successfully");
}
void setLogger(ILogger* l) {
logger = l; // Can swap logger at runtime!
}
};
int main() {
ConsoleLogger consoleLog;
FileLogger fileLog("app.log");
Application app("MyApp", &consoleLog);
app.run();
// Switch to file logger at runtime
app.setLogger(&fileLog);
app.run();
return 0;
}
7. Common Mistakes
Mistake 1: Using Inheritance for Code Reuse Only
// WRONG - Using inheritance just for code reuse
class ArrayList {
public:
void add(int item) { /* ... */ }
int get(int index) { /* ... */ }
};
class Stack : public ArrayList { // Stack is NOT an ArrayList!
public:
void push(int item) { add(item); }
int pop() { /* ... */ }
};
// Problem: Stack now exposes get(index) which breaks stack semantics
// CORRECT - Using composition
class ArrayList {
public:
void add(int item) { /* ... */ }
int get(int index) { /* ... */ }
int removeLast() { /* ... */ }
};
class Stack {
private:
ArrayList list; // HAS-A relationship
public:
void push(int item) { list.add(item); }
int pop() { return list.removeLast(); }
// get(index) is NOT exposed - proper encapsulation
};
Mistake 2: Deep Inheritance Hierarchies
// WRONG - Too deep hierarchy
class Animal { };
class Mammal : public Animal { };
class Carnivore : public Mammal { };
class Feline : public Carnivore { };
class Cat : public Feline { };
class DomesticCat : public Cat { };
class PersianCat : public DomesticCat { }; // 7 levels deep!
// BETTER - Flatten with composition
class Animal {
DietType diet; // HAS-A
ReproductionType repro; // HAS-A
BehaviorSet behaviors; // HAS-A
};
class Cat : public Animal {
BreedInfo breed; // HAS-A
};
Mistake 3: Violating Liskov Substitution Principle
// WRONG - Square is not a proper Rectangle
class Rectangle {
protected:
int width, height;
public:
virtual void setWidth(int w) { width = w; }
virtual void setHeight(int h) { height = h; }
int getArea() { return width * height; }
};
class Square : public Rectangle {
public:
void setWidth(int w) override {
width = w;
height = w; // Forces both to be equal
}
void setHeight(int h) override {
width = h;
height = h; // Forces both to be equal
}
};
// This breaks!
void resize(Rectangle& r) {
r.setWidth(5);
r.setHeight(10);
assert(r.getArea() == 50); // Fails for Square!
}
8. Interview Questions
Q1: What is the difference between IS-A and HAS-A relationships?
Answer:
- IS-A (Inheritance): Represents a "type of" relationship where a child class derives from a parent class. Example: Dog IS-A Animal.
- HAS-A (Composition): Represents a "contains" relationship where one class has another class as a member. Example: Car HAS-A Engine.
Q2: Why is composition preferred over inheritance?
Answer:
Composition is preferred because:
- Loose coupling: Components can be changed independently
- Flexibility: Behavior can be changed at runtime
- Better encapsulation: Internal details are hidden
- Easier testing: Dependencies can be easily mocked
- Avoids fragile base class problem: Parent changes don't break children
Q3: Can we use both inheritance and composition together?
Answer:
Yes, and this is the recommended approach in real systems:
- Use inheritance for genuine type hierarchies and polymorphism
- Use composition for combining behaviors and functionality
Example: IndianTax IS-A TaxCalculator (inheritance) + Invoice HAS-A TaxCalculator (composition)
Q4: What is the difference between Composition and Aggregation?
Answer:
| Aspect | Composition | Aggregation |
|--------|-------------|-------------|
| Ownership | Strong - parent owns child | Weak - parent uses child |
| Lifecycle | Child dies with parent | Child survives parent |
| Example | Human-Heart | Team-Player |
Q5: When should you use inheritance?
Answer:
Use inheritance when:
- There is a genuine IS-A relationship
- Liskov Substitution Principle is satisfied
- You need runtime polymorphism
- The relationship is stable and unlikely to change
Q6: What is the Diamond Problem? How do you solve it?
Answer:
The diamond problem occurs in multiple inheritance when a class inherits from two classes that have a common ancestor.
class A { public: void show() { } };
class B : public A { };
class C : public A { };
class D : public B, public C { }; // Diamond problem - which A::show()?
Solution: Use virtual inheritance
class A { public: void show() { } };
class B : virtual public A { };
class C : virtual public A { };
class D : public B, public C { }; // Only one copy of A
Q7: How do you decide between IS-A and HAS-A?
Answer:
Use this decision framework:
-
Sentence Test:
- If "X is a Y" makes sense → Inheritance
- If "X has a Y" makes sense → Composition
-
Behavior Test:
- Can child substitute parent everywhere? → Inheritance
- Does class need to use another class's features? → Composition
-
Change Test:
- Is the relationship permanent? → Inheritance
- Might the relationship change? → Composition
Quick Decision Flowchart
Start
|
v
Does "X is a Y" make sense?
/ \
Yes No
| |
v v
Can X substitute Does X need
Y everywhere? Y's features?
/ \ / \
Yes No Yes No
| | | |
v v v v
Inheritance Composition Composition No relationship
Self-Test Exercises
Determine the correct relationship for each:
| Statement | Your Answer | Correct Answer |
|---|---|---|
| Dog and Animal | Inheritance | |
| Car and Engine | Composition | |
| Manager and Employee | Inheritance | |
| Order and Payment | Composition | |
| Circle and Shape | Inheritance | |
| University and Department | Composition | |
| SavingsAccount and BankAccount | Inheritance | |
| Computer and CPU | Composition | |
| Penguin and Bird | Inheritance | |
| Library and Book | Aggregation |
Summary
| Concept | Key Point |
|---|---|
| IS-A (Inheritance) | Child is a specialized type of parent |
| HAS-A (Composition) | Class contains/uses another class |
| Composition | Strong ownership, part dies with whole |
| Aggregation | Weak ownership, part survives whole |
| Golden Rule | Prefer composition over inheritance |
| Use Inheritance When | True IS-A + LSP satisfied + Need polymorphism |
| Use Composition When | Need flexibility + Loose coupling + Runtime changes |
Remember:
Inheritance = IS-A relationship
Composition = HAS-A relationship
Use composition by default
Use inheritance only when logically required and LSP is satisfied
Top comments (0)