DEV Community

Cover image for OOP in C++ Explained: IS-A vs HAS-A Relationship (Inheritance vs Composition Guide)
Rajat Sharma
Rajat Sharma

Posted on

OOP in C++ Explained: IS-A vs HAS-A Relationship (Inheritance vs Composition Guide)

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

  1. IS-A Relationship (Inheritance)
  2. HAS-A Relationship (Composition)
  3. Composition vs Aggregation
  4. Key Differences
  5. Design Principles
  6. Real-World Examples
  7. Common Mistakes
  8. 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;
}
Enter fullscreen mode Exit fullscreen mode

Output:

Parent function
Child function
Enter fullscreen mode Exit fullscreen mode

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; }
};
Enter fullscreen mode Exit fullscreen mode

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; }
};
Enter fullscreen mode Exit fullscreen mode

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; }
};
Enter fullscreen mode Exit fullscreen mode

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; }
};
Enter fullscreen mode Exit fullscreen mode

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; }
};
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

Output:

Dog barks: Woof!
Cat meows: Meow!
Cow moos: Moo!
Enter fullscreen mode Exit fullscreen mode

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!
Enter fullscreen mode Exit fullscreen mode

Inheritance Access Specifiers

class Base {
public:
    int publicVar;
protected:
    int protectedVar;
private:
    int privateVar;
};
Enter fullscreen mode Exit fullscreen mode
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;
}
Enter fullscreen mode Exit fullscreen mode

Output:

Starting Tesla Model 3...
Engine started
Engine stopped
Tesla Model 3 stopped
Enter fullscreen mode Exit fullscreen mode

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();
    }
};
Enter fullscreen mode Exit fullscreen mode

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
};
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

Output:

Starting Honda Civic
Petrol engine: Vroom!
Starting Tesla Model 3
Electric engine: Whirr...
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

5. Design Principles

Golden Rule (Industry Standard)

"Prefer composition over inheritance"
— Gang of Four (Design Patterns Book)

Why Composition is Preferred

  1. Loose Coupling: Components can be changed independently
  2. Flexibility: Behavior can be changed at runtime
  3. Testability: Easy to mock dependencies
  4. Single Responsibility: Each class has one reason to change
  5. Avoids Fragile Base Class: Changes to parent don't break children

When to Use Inheritance

Use inheritance only when:

  1. There is a genuine IS-A relationship
  2. You need runtime polymorphism
  3. The relationship is unlikely to change
  4. 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
    }
};
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

Relationships:

  • IndianTax IS-A ITaxCalculator (Inheritance)
  • CreditCardProcessor IS-A IPaymentProcessor (Inheritance)
  • Order HAS-A ITaxCalculator (Composition)
  • Order HAS-A IPaymentProcessor (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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
// 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
};
Enter fullscreen mode Exit fullscreen mode

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!
Enter fullscreen mode Exit fullscreen mode
// 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
};
Enter fullscreen mode Exit fullscreen mode

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!
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. Loose coupling: Components can be changed independently
  2. Flexibility: Behavior can be changed at runtime
  3. Better encapsulation: Internal details are hidden
  4. Easier testing: Dependencies can be easily mocked
  5. 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:

  1. There is a genuine IS-A relationship
  2. Liskov Substitution Principle is satisfied
  3. You need runtime polymorphism
  4. 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()?
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Q7: How do you decide between IS-A and HAS-A?

Answer:
Use this decision framework:

  1. Sentence Test:

    • If "X is a Y" makes sense → Inheritance
    • If "X has a Y" makes sense → Composition
  2. Behavior Test:

    • Can child substitute parent everywhere? → Inheritance
    • Does class need to use another class's features? → Composition
  3. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Top comments (0)