Composition vs Inheritance in OOP: Practical Guidelines for Object Design

Composition vs Inheritance: Basic Distinction

Welcome back! Today we are tackling one of the most fundamental design decisions in object-oriented programming. It's a choice that defines how your software grows—and how easily it breaks.

Approach 1

Inheritance

The "Is-A" Relationship. Think of this as modifying a pre-built vehicle. If you have a generic Vehicle, a Car is a vehicle.

You get everything the parent has (wheels, engine). But you are locked into that structure. If the parent changes, you change.

Approach 2

Composition

The "Has-A" Relationship. Think of this as gathering independent parts. A car has an engine, has wheels.

You assemble what you need. Need an electric motor? Swap the engine. The car doesn't care how the engine works, only that it runs.

The Inheritance Trap

It feels natural to use inheritance for code reuse. "I have an Employee and a Manager, so Manager extends Employee." It works until you need a Contractor who gets paid differently but isn't strictly an Employee.

Inheritance creates tight coupling. If you change how Employee calculates pay, you might accidentally break the Manager. You are stuck with a rigid family tree.

Interactive Demo

The Composition Solution: Plug-and-Play

Object
Employee
name: "Alex"
HAS A
Component
SalariedPay
Fixed monthly rate
Connected!

Click a button to swap the internal component without changing the Employee class:

Currently using SalariedPay. The Employee class remains unchanged.

Key Takeaway

Inheritance should be reserved for clear, permanent "is-a" relationships. If you need flexibility—like swapping out how an employee gets paid—use composition. It allows you to change behavior at runtime without breaking the entire hierarchy.

Core Concepts of OOP Composition

Now that we know why we prefer composition, let's look at how it actually works under the hood. I like to think of this as the difference between sculpting clay and building with Legos.

Inheritance (Sculpting)

Imagine a single block of clay. To make a "Manager," you carve it out of the "Employee" block.

The Limitation: You can't swap the clay mid-sculpt. If you want to turn the Manager into a Contractor, you have to start over or carve a new, complex shape.

Composition (Lego)

Think of Lego bricks. A car is just a collection of bricks (wheels, engine, chassis).

The Power: If you want an electric car, you just swap the "Gas Engine" brick for an "Electric Motor" brick. The rest of the car stays exactly the same.

The "Has-A" Trap

Beginners often say, "Composition is just a has-a relationship." That's true, but it's incomplete.

It's not enough to just have an object. You must delegate work to it. If your Car class has an Engine object but still writes all the engine logic inside the Car class, you haven't achieved composition—you've just moved variables around.

True composition means the Car says: "I don't know how to start the engine. I will ask the Engine object to do it."

Visualizing the Contract

The Interface Contract

The Car depends on the Interface (the contract), not the specific implementation. Try swapping the engine type below.

Client
Car
calls start()
DEPENDS ON
Interface
Implementation
GasEngine
Ignites spark plugs...
> Car starting... Engine: GasEngine initialized.

Key Takeaway

Composition is about delegation. By coding to an interface (a contract), you allow different objects to plug in at runtime. The Car doesn't care what the engine is, only that it knows how to start().

Favor Composition over Inheritance: Practical Guidelines

Welcome back. We've established that composition is flexible. Now, let's get practical. How do we actually apply this? The golden rule is simple: Don't let your class inherit behavior; give it the tools to perform it.

The Car Analogy: Delegation vs. Implementation

Think of your Car class as a conductor. It doesn't play the instruments; it tells them when to play.

When you design Car, ask: "What must this object do?" (e.g., start, move). Then, extract those actions into separate interfaces (Engine, Transmission). The Car holds references to these interfaces and says, "I don't implement start() myself—I ask my Engine to do it."

Practical Guideline: Compose objects from roles (interfaces), not from hierarchies (classes).

Interactive Simulation

The Coupling Trap: Inheritance vs. Composition

A common misconception is that inheritance is "loose" because it reuses code. In reality, it creates implementation coupling. Try modifying the Engine below in both modes to see what happens to the Car.

Client
Car
Stable
IS A PART OF
Implementation
V8 Engine
Internal Logic
Trigger an internal change in the Engine (e.g., switch from V8 to Hybrid):
> System initialized. Car is using V8 Engine.

Benefits in Maintainability

This loose coupling directly improves maintainability in three critical ways.

1

Localized Changes

Modify ElectricMotor without touching Car. In an inheritance tree, a change high in the hierarchy ripples down unpredictably.

2

Easier Testing

You can test Car by passing a mock Engine that returns predetermined results. No need to set up complex parent state.

3

Adaptability

Need a Robot that schedules tasks like an Employee but isn't human? Give it a Scheduler component. No awkward inheritance needed.

Final Thought

The guideline isn't "never use inheritance." It's "use inheritance only for clear, stable is-a relationships." For behaviors that mix, change, or are shared across unrelated classes—compose with interfaces.

Inheritance vs Composition: When to Use Each

Hello again! Now that we understand what composition is, the big question remains: When do I use it?

Design patterns aren't about following rigid laws; they are about asking the right questions. The most powerful tool in your arsenal is actually your native language.

The Linguistic Test: "Is-A" vs. "Has-A"

Before writing a single line of code, try to describe the relationship between two classes in English.

1

The "Is-A" Test

Can I truthfully say: "A [Subclass] is a [Superclass]"?

"A Dog is an Animal."
"A SavingsAccount is a BankAccount."
✅ Verdict: Use Inheritance
2

The "Has-A" Test

Can I truthfully say: "A [Object] has a [Component]"?

"A Car has an Engine."
"A User has a NotificationService."
✅ Verdict: Use Composition

Professor's Tip: If the sentence feels forced, stop! If you say "A Contractor is an Employee" but they don't get paid the same way or take the same vacation, you've just created a lie. That lie will come back to haunt you as bugs.

The Trap: Forcing Inheritance

The most common mistake beginners make is using inheritance just to share code (like a `name` field or `calculatePay()` method), even when the relationship isn't truly "is-a".

The Mistake

// ❌ Problem: Contractor IS NOT an Employee

class Person {
  String name;
  void calculatePay() { ... }
}

class Employee extends Person { ... }
class Contractor extends Person { ... }

Here, Contractor inherits everything from Person. But what if Person has a method takeVacation()? A contractor doesn't take vacations—they take breaks!

⚠️ Fragile: You've created a false hierarchy. Changing Person might break Contractor.

The Composition Fix

Instead of forcing a family tree, extract the shared behavior into a component.

interface PayCalculator {
  double calculate();
}

// Independent classes
class Employee {
  private PayCalculator payroll;
}

class Contractor {
  private PayCalculator payroll;
}

Now, Employee and Contractor are independent. They both have a payroll service, but neither is a "Person".

✅ Robust: You can change how pay is calculated without touching the classes.

When Inheritance Simplifies Code

Don't let me scare you! Inheritance is still useful, but only when the hierarchy is stable and obvious.

If you have a generic Animal class with eat() and sleep(), and a Dog class, inheritance is perfect. A Dog will always be an Animal, and it will always eat and sleep.

  • The relationship is permanent.
  • The parent behavior is generic and stable.

// ✅ Good: Stable Hierarchy

class Animal {
  void eat() { ... }
  void sleep() { ... }
}

class Dog extends Animal {
  // Dog inherits eat() & sleep()
  void bark() { ... }
}
Interactive Decision Tool

The "Is-A" vs "Has-A" Decision Tree

Imagine you are designing a system. Use this tool to decide the relationship between two objects.

Object A
Dog
Relationship
Object B
Animal
Recommendation
Inheritance

"Dog is an Animal" is a true statement. The hierarchy is stable.

Final Thought

The rule of thumb: Use inheritance only for clear, stable "is-a" relationships. If you're inheriting just to share code, or if the relationship is "has-a" or "uses-a", reach for composition. It might take a few more lines of code initially, but it will save you hours of debugging later.

Object Composition Tutorial: Building Flexible Objects

Welcome back. Now that we understand the theory, let's get our hands dirty. How do we actually build with composition? I like to think of this as assembling a high-quality puzzle.

The Puzzle Piece Analogy

Imagine a picture of a landscape. You have a "Sky" piece, a "Tree" piece, and a "House" piece.

In inheritance, you carve the house out of the landscape. If you want to change the house to a bridge, you have to carve the whole landscape again.

In composition, you just swap the piece. The bridge snaps into the same spot. The edges (interfaces) are the same, but the content is different.

Key Concept: Composition is about snapping together independent, focused modules. Each piece does one thing and fits with others through well-defined edges.

The Trap: Overcomplicating

Beginners often hear "favor composition" and break every class into dozens of tiny components. This creates unnecessary indirection.

If a User class only ever sends email notifications and never changes, creating an interface and five different implementations is overkill. You've added complexity for zero benefit.

Rule of Thumb: Compose when you anticipate change or reuse. If a responsibility is stable and unique, keep it internal.
Interactive Refactoring

From Rigid Code to Flexible Composition

Watch how we extract a responsibility. Initially, the User class handles everything. We will extract the "Notification" logic into a separate component.

Class: User
+ sendEmail(msg)
+ sendSMS(msg)
+ saveToDB()
Contains
Interface: Notifier
EmailSender
Implementation
> System initialized. User class contains all logic.

Step-by-Step Composition Thinking

When you design an object, follow this mental sequence to decide what to compose:

1

Identify Responsibilities

What distinct actions does this object need? (e.g., a User needs to send notifications, calculate discounts, log activity).

2

Ask: "Will this change?"

If sendNotification() might support SMS, Push, or Email in the future, it's a candidate for composition. If logActivity() is simple and stable, keep it internal.

3

Extract an Interface

Define a contract (e.g., NotificationSender) with the method send(). This is your "puzzle edge."

4

Delegate

Your User holds a reference to the interface and calls sender.send() instead of implementing the logic itself.

The Result: Delegation in Action

Notice the power: User never knows how the message is sent. It only knows it can ask its notifier to send. Add a PushSender later? No change to User.

interface NotificationSender {
    void send(String message);
}

class EmailSender implements NotificationSender {
    public void send(String message) {
        // email-specific logic: SMTP, formatting, etc.
    }
}

class User {
    private String name;
    private NotificationSender notifier; // Composition

    public User(String name, NotificationSender notifier) {
        this.name = name;
        this.notifier = notifier; // Inject behavior
    }

    public void alert(String message) {
        notifier.send(message); // Delegate, don't implement
    }
}

Final Thought

Remember: Compose when you need to swap behaviors, share code across unrelated classes, or isolate volatile logic. Don't compose just to avoid a few duplicated lines—sometimes a simple, self-contained class is the most maintainable choice.

Advanced: The Fragile Base Class Problem

Welcome back. We've established that composition is generally safer. But you might ask, "Professor, is inheritance ever actually okay?"

The answer is yes—but only in narrow, well-defined scenarios. The danger lies in the Fragile Base Class Problem. Let's visualize why deep inheritance hierarchies are often a ticking time bomb.

The "Safe Zone": When to Inherit

Inheritance is acceptable only when the hierarchy is shallow and semantically pure.

1. Unambiguous "Is-A"

Dog is an Animal. There is no scenario where a Dog stops being an Animal.

2. Stable Extension

The parent class is designed for subclasses (e.g., `HttpServlet`). Its internal implementation won't change unexpectedly.

3. Flat Hierarchy

Keep it to 1-2 levels. Vehicle -> Car -> SportsCar is already too deep.

Simulation

The Fragile Base Class Problem

This happens when a subclass relies on the internal implementation of its parent. If the parent changes its internal logic, the child breaks—even if the public contract didn't change.

Parent Class
Employee
protected double computeBonus() {
  return salary * 0.1;
}
EXTENDS
Child Class
Manager
@Override
protected double computeBonus() {
  return super.computeBonus() * 2;
}
System initialized. Manager bonus = (Salary * 0.1) * 2

The Refactoring Strategy: Composition

To fix this, we stop inheriting behavior and start composing it. We extract the volatile part (bonus calculation) into a separate interface.

interface BonusCalculator {
  double calculate(double salary);
}

Now, Employee doesn't know how to calculate a bonus. It just has a calculator.

class Employee {
  private BonusCalculator calc;

  public Employee(BonusCalculator calc) {
    this.calc = calc;
  }

  double getPay() {
    return base + calc.calculate(base);
  }
}

Why This Works

  • Encapsulation: Changing the calculator logic doesn't touch the Employee class.
  • Independence: A StandardBonus calculator is completely separate from a ManagerBonus calculator.
  • Flexibility: You can swap calculators at runtime without breaking inheritance chains.
Refactored Solution

Decoupled via Interfaces

Here, the Employee class is stable. We can change the calculator implementation without affecting the Employee structure.

Stable Class
Employee
private BonusCalculator calc;
double getPay() {
  return calc.calculate();
}
DEPENDS ON
Strategy
StandardBonus
Logic: Salary * 0.1

Swap the calculator implementation:

> Employee using StandardBonus. Structure remains stable.

Final Thought

When you feel the need to override a non-final method in a parent class, pause. Ask: "Am I trying to change the parent's implementation?" If yes, that coupling is the problem. Extract that varying part into its own interface and compose with it instead. This transforms a fragile tree into a flexible graph of collaborating objects.

Common Misconceptions Across OOP Design

Welcome back! We've covered the mechanics of composition, but before you start refactoring your entire codebase, we need to address the myths. There are persistent misconceptions about when to use inheritance that trip up even experienced developers.

The Myth: "Inheritance is for Reuse"

Many beginners learn to "extend" a class simply to copy-paste code. "I need a name field, so I'll extend Person."

The Problem: This creates a "False Bond." You are tying two classes together by their internal implementation, not their meaning. If the parent changes, you break.

The Reality: "Inheritance is for Meaning"

Inheritance is for modeling permanent, conceptual relationships. A Dog is a Animal. That is true forever.

The Rule: If you need to share code, extract it into a component. If you need to model a concept, use inheritance. Never mix the two purposes.

The Trap: The "Contractor" Fallacy

The most common mistake is creating an inheritance hierarchy where none should exist, simply because two classes share fields. Let's visualize the "Contractor Trap."

The Mistake

The Leaky Abstraction

You create a Person class to share the name field. But Person also has a takeVacation() method.
Since Contractor extends Person, it inherits that method. But Contractors don't get paid vacation!

Base Class
Person
void takeVacation() {
  payVacation();
}
EXTENDS
Child Class
Contractor
inherits everything
even bad methods!
> System ready. Waiting for input...

The Fix: Composition

Instead of forcing Contractor to be a Person, give it a PayCalculator and a Identity component.

Key Takeaway: If you feel the need to override a method just to make it do nothing (or throw an error), you have created a false hierarchy.

How to Test Your Design Decisions

Don't guess. Use this mental checklist to decide between inheritance and composition. Try the scenarios below.

Interactive Tool

The Design Decision Matrix

Verdict
Inheritance

"Dog is an Animal" is a true statement. The hierarchy is stable.

Final Thought

Remember the rule: Start with composition. Only switch to inheritance when the "is-a" is unmistakable, the hierarchy is shallow, and the parent is explicitly designed for extension. If you're hesitating, compose. It keeps your design flexible and your objects loosely coupled.

Real-World Case Study: Refactoring from Inheritance to Composition

Welcome back. Now let's get real. Theory is great, but how does this look in a messy, production codebase? We are going to look at a classic scenario: The Reporting System.

Imagine you are building a system to generate reports. At first, it seems simple. You have PDF reports, HTML reports, and CSV reports. You create an inheritance hierarchy. It feels tidy. But then... the requirements change.

The Trap: Combinatorial Explosion

Initially, you have 3 formats (PDF, HTML, CSV). Then Marketing says: "We need reports from different Data Sources (API, Database, File)." Then Security says: "We need Encrypted reports for sensitive data."

With inheritance, you can't mix and match. You need a specific class for every combination.
3 Formats × 3 Sources × 2 Security Levels = 18 Classes.

Required Classes
1
Classes to maintain

The Solution: Flexible Assembly

Instead of creating a new class for every combination, we compose the behavior. We extract the changing parts into interfaces: Formatter, DataSource, Security.

Build Your Report at Runtime

Notice: We only need one Report class. We just swap the components.

Class
Report
holds components
Formatter
DataSource
Security
> Report initialized. Ready to generate.

Practical Steps for Safe Migration

You might be thinking: "But my code is already written! I can't just throw it away."
You don't have to. Refactoring is about safe, incremental steps.

1

Lock Down Tests

Before touching code, write tests for your existing inheritance behavior. These are your safety net. If a refactoring step breaks a test, you know immediately.

2

Extract the Interface

Identify the varying behavior (e.g., formatting). Create an interface (e.g., ReportFormatter). Don't change the parent class yet.

3

Move Logic to a Component

Take the logic from one subclass (e.g., PDFReport) and move it into a new class that implements the interface (e.g., PDFFormatter).

4

Inject and Verify

Modify the parent class to accept the component. Update the subclass to use it. Run tests. Repeat for other behaviors.

The Old Way (Inheritance)
class PDFReport extends Report {
    void generate() {
        // Hardcoded PDF logic
        // Hardcoded DB logic
        // Hardcoded Encryption logic
    }
}

Rigid. Changing any logic requires creating a new subclass.

The New Way (Composition)
class Report {
    private ReportFormatter fmt;
    private DataSource source;
    private Security sec;

    void generate() {
        String data = source.fetch();
        String formatted = fmt.format(data);
        String final = sec.encrypt(formatted);
    }
}

Flexible. Change behavior by swapping components, not rewriting code.

Final Thought

The fear of refactoring is natural, but the risk of not refactoring is higher. Inheritance creates a fragile tree that grows brittle over time. Composition builds a flexible graph that grows stronger. Start small, test often, and watch your code become resilient.

Checklist for Choosing Composition vs Inheritance

Welcome back. Now comes the moment of truth. You have the tools, but how do you choose? When you stand before a blank file, staring at a new class, which path do you take?

Don't guess. Use this mental checklist. I've designed a quick decision tool below to help you run through these questions before you write a single line of code.

Interactive Decision Helper

The Decision Flow

Select a scenario below. The tool will run through the 6 checklist questions and give you a recommendation.

Recommendation
Inheritance

The "is-a" relationship is clear and permanent.

> Running checklist... Waiting for input.

The 6 Decision Factors

When you aren't using the tool, run through these questions manually. They are designed as a mental flowchart to quickly steer you toward the right approach.

1

Is the relationship a clear, permanent "is-a"?

Ask yourself: "Can I honestly say 'X is a Y' in the real-world domain?"

  • If yes (e.g., SavingsAccount is a BankAccount), inheritance is a candidate.
  • If no or it feels forced (e.g., Contractor is an Employee), reject inheritance.
2

Will the behavior vary or be reused across unrelated classes?

Think: "Is this something I might want to swap out, or use in a different context?"

  • If yes (e.g., payment calculation, notification sending), extract an interface and compose.
  • If no—the behavior is stable, simple, and unique to this class—keep it internal.
3

Is the parent class explicitly designed and documented for inheritance?

Check: "Does the class author intend for me to subclass it?" Look for @Override-friendly methods and stability guarantees.

  • If yes (e.g., Java's HttpServlet), inheritance may be safe.
  • If no (e.g., a utility class), avoid inheritance—it's likely not built to handle fragile base class issues.
4

Do I need to mix this behavior with other, unrelated behaviors?

Consider: "Will this object need to combine this capability with something from a different hierarchy?"

  • If yes (e.g., a Robot with Employee-like scheduling AND Vehicle movement), you must compose.
  • If no—the object belongs to a single, pure hierarchy—inheritance could be acceptable.
5

Am I tempted to override a non-final method and call super?

This is a red flag. Overriding implementation details (not just abstract methods) couples you to the parent's internal logic.

  • If yes, extract the varying part into its own interface and compose instead.
  • If no, you're likely just implementing abstract methods—that's what inheritance is for.
6

Is the class cohesive, or am I breaking it into too many pieces?

Cohesion means a class has a single, well-focused responsibility.

  • If the class is highly cohesive (all its methods directly serve one purpose), don't force composition just to avoid a few duplicated lines.
  • If the class is doing multiple unrelated things (e.g., handling business logic AND formatting AND data access), that's a sign to extract components and compose.

Common Pitfall: Ignoring Cohesion

The checklist above pushes you toward composition for flexibility. But beginners sometimes overapply it, splitting a naturally cohesive class into needless fragments.

Example: A User class that only ever sends email notifications—and that logic is simple and unlikely to change. Extracting a NotificationSender interface, EmailSender class, and dependency injection is overengineering. The class remains cohesive: its sole job is managing user data AND emailing users.

Rule: Compose to isolate variation, not to avoid duplication per se. If a responsibility is stable, unique to this class, and tied to its core identity... keep it inside.

Quick Reference Guide

Situation Choose... Why
Clear "is-a" with stable parent, no mixing needed Inheritance Models true specialization; simple when hierarchy is shallow.
"Has-a" relationship, or need to swap implementations Composition Loose coupling via interfaces; flexible at runtime.
Sharing code between classes that aren't conceptually related Composition Avoids false hierarchy; use interfaces to extract shared behavior.
Need to combine multiple behaviors (e.g., Robot + Scheduler + Mover) Composition Inheritance gives only one parent; composition assembles many roles.
Parent class isn't designed for inheritance (no docs, frequent changes) Composition Prevents fragile base class problem.
Class is cohesive and behavior won't change Keep Internal Don't overcompose; simplicity wins when no variation expected.

Final Heuristic

Start with composition. Only switch to inheritance when all these are true:
1. The "is-a" is undeniable.
2. The parent is stable and meant to be subclassed.
3. You're not mixing in other behaviors.
4. You're not overriding implementation details.

If you hesitate on any point—compose.

Frequently Asked Questions (FAQ)

Welcome back. You've reached the FAQ section. These are the exact questions I hear from students and junior developers every day. Let's clear up the confusion once and for all.

Q1 What is the difference between composition and inheritance?

Inheritance (The "Is-A" Relationship)

A subclass is a specialized version of its parent (e.g., Dog is an Animal).

Impact: It shares implementation directly, tightly coupling the child to the parent's internal code.

Composition (The "Has-A" Relationship)

An object has other objects as components (e.g., Car has an Engine).

Impact: It delegates work via interfaces, decoupling the what from the how.

Q2 Why does my inheritance hierarchy cause bugs?

You are likely encountering the Fragile Base Class Problem. When a subclass overrides a method and calls super.method(), it becomes dependent on the parent's internal implementation, not just its public contract.

Simulation
Parent Class
Logic: return a + b;
EXTENDS
Child Class
Logic: return super() * 2;
> System stable. Child correctly calculates result.

Why it breaks: If the parent changes logic (e.g., return a * b;), the child's result changes unexpectedly. Composition avoids this by depending only on stable interfaces.

Q3 When should I choose composition over inheritance?

Choose composition when:

  • The relationship is "has-a" rather than "is-a".
  • You need to swap behaviors at runtime (e.g., different payment strategies).
  • You want to share code across unrelated classes (e.g., Employee and Contractor both need pay calculation).
  • You need to combine multiple independent behaviors (e.g., a Robot with both Scheduler and Mover).

Professor's Tip: If you're unsure, start with composition. Only use inheritance for clear, permanent "is-a" relationships with shallow, stable hierarchies.

Q4 Can I still use inheritance effectively in small projects?

Yes, but cautiously. Inheritance is acceptable in small projects only if:

1. The "is-a" relationship is unambiguous and permanent (e.g., Square is a Shape).
2. The parent class is designed for extension (e.g., a framework base class with protected hooks).
3. The hierarchy is shallow (one or two levels max) and unlikely to grow.
4. You're not overriding implementation details—only implementing abstract methods.

Even in small projects, avoid using inheritance merely to share code between classes that aren't conceptually related. That technical debt compounds quickly as the project grows.

Q5 How does composition affect performance?

Negligibly in practice. Composition introduces a level of indirection—your object holds a reference to another object and delegates calls. This is one extra pointer dereference, which is trivial compared to I/O, database, or network operations.

Modern JIT compilers often inline these calls. The performance cost is far lower than the maintenance cost of a fragile inheritance hierarchy.

Rule: Prioritize design clarity; optimize only if profiling shows a real bottleneck (rare).

Q6 What are common pitfalls when refactoring to composition?

!

Overcomposing

Breaking every class into tiny components prematurely, creating unnecessary indirection for stable, cohesive logic.

!

Leaking Abstractions

Exposing component interfaces in the main class's public API when they should be private.

!

Ignoring Tests

Refactoring without a safety net of automated tests.

!

Partial Refactoring

Mixing inheritance and composition in the same hierarchy, which can worsen coupling.

Q7 Is there a rule of thumb for deciding between the two?

Start with composition. Only use inheritance when all are true:

  1. "Is-a" is undeniable—the subclass is a true subtype in the domain model.
  2. Parent is stable and designed for inheritance—documented as subclassable, with final or rarely changing implementation.
  3. No mixing needed—you won't need to combine this behavior with unrelated ones.
  4. No implementation overriding—you only implement abstract methods, never override concrete logic to change parent behavior.

If you hesitate on any point, compose. This heuristic keeps designs flexible and avoids the fragile base class trap.

Post a Comment

Previous Post Next Post