Menu
Coddy logo textTech

Composition vs Inheritance

Part of the Object Oriented Programming section of Coddy's Dart journey — lesson 82 of 110.

You've learned that inheritance creates an "is-a" relationship - a Dog is an Animal. But there's another powerful approach: composition, which creates a "has-a" relationship. Instead of inheriting behavior, a class contains instances of other classes and delegates work to them.

Consider a Car class. With inheritance, you might try to extend an Engine class - but a car isn't an engine, it has an engine:

class Engine {
  void start() => print('Engine started');
}

class Car {
  final Engine _engine = Engine();  // Composition: Car HAS an Engine
  
  void start() {
    _engine.start();  // Delegate to the engine
    print('Car is ready to drive');
  }
}

void main() {
  var car = Car();
  car.start();
}

The key advantage of composition is flexibility. With inheritance, you're locked into a single parent class. With composition, you can combine multiple components and even swap them at runtime:

class ElectricEngine {
  void start() => print('Electric motor humming');
}

class HybridCar {
  Engine? _gasEngine;
  ElectricEngine? _electricEngine;
  
  HybridCar({bool useElectric = false}) {
    if (useElectric) {
      _electricEngine = ElectricEngine();
    } else {
      _gasEngine = Engine();
    }
  }
}

A common guideline is "favor composition over inheritance". Use inheritance when there's a true "is-a" relationship and you need polymorphism. Use composition when you want to reuse behavior without tight coupling, or when an object needs capabilities from multiple sources.

challenge icon

Challenge

Easy

Let's build a computer system using composition! Instead of trying to make a computer inherit from its parts, you'll create separate component classes and compose them together - because a computer has a CPU and has memory, it isn't a CPU or memory.

You'll organize your code into three files:

  • components.dart: Create two component classes that represent computer parts:
    • A CPU class with a String brand and double speedGHz. Include a process() method that returns a string: [brand] processing at [speedGHz]GHz
    • A Memory class with an int sizeGB. Include a load() method that returns a string: Loading [sizeGB]GB of data
  • computer.dart: Create a Computer class that uses composition to combine the components. Your computer should:
    • Have a String name field
    • Contain a CPU and a Memory instance (composition - the computer HAS these parts)
    • Accept all necessary values through its constructor to create both components internally
    • Have a boot() method that delegates to both components and prints three lines: the computer's name followed by starting..., then the result of the CPU's process method, then the result of the Memory's load method
    • Have a specs() method that returns a string describing the system: [name]: [brand] [speedGHz]GHz, [sizeGB]GB RAM
  • main.dart: Import your files and demonstrate composition in action:
    • Create a Computer named Workstation with an Intel CPU running at 3.5 GHz and 16 GB of memory
    • Call the boot() method
    • Print the result of specs()

Notice how the Computer class doesn't extend CPU or Memory - it contains them and delegates work to them. This is the "has-a" relationship that composition creates!

Expected output:

Workstation starting...
Intel processing at 3.5GHz
Loading 16GB of data
Workstation: Intel 3.5GHz, 16GB RAM

Cheat sheet

Composition creates a "has-a" relationship where a class contains instances of other classes and delegates work to them, rather than inheriting from them.

Basic composition example:

class Engine {
  void start() => print('Engine started');
}

class Car {
  final Engine _engine = Engine();  // Car HAS an Engine
  
  void start() {
    _engine.start();  // Delegate to the engine
    print('Car is ready to drive');
  }
}

Composition with flexibility - swapping components:

class ElectricEngine {
  void start() => print('Electric motor humming');
}

class HybridCar {
  Engine? _gasEngine;
  ElectricEngine? _electricEngine;
  
  HybridCar({bool useElectric = false}) {
    if (useElectric) {
      _electricEngine = ElectricEngine();
    } else {
      _gasEngine = Engine();
    }
  }
}

When to use composition vs inheritance:

  • Use inheritance for true "is-a" relationships when you need polymorphism
  • Use composition to reuse behavior without tight coupling, or when an object needs capabilities from multiple sources
  • General guideline: "favor composition over inheritance"

Try it yourself

import 'computer.dart';

void main() {
  // TODO: Create a Computer named "Workstation" with:
  // - Intel CPU running at 3.5 GHz
  // - 16 GB of memory

  // TODO: Call the boot() method

  // TODO: Print the result of specs()
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming