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
EasyLet'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
CPUclass with aString brandanddouble speedGHz. Include aprocess()method that returns a string:[brand] processing at [speedGHz]GHz - A
Memoryclass with anint sizeGB. Include aload()method that returns a string:Loading [sizeGB]GB of data
- A
computer.dart: Create aComputerclass that uses composition to combine the components. Your computer should:- Have a
String namefield - Contain a
CPUand aMemoryinstance (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 bystarting..., 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
- Have a
main.dart: Import your files and demonstrate composition in action:- Create a
ComputernamedWorkstationwith anIntelCPU running at3.5GHz and16GB of memory - Call the
boot()method - Print the result of
specs()
- Create a
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 RAMCheat 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()
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesLibraries & ImportsIntroduction to OOPClasses vs ObjectsThe this KeywordMethodsInstance VariablesConstructor BasicsRecap - Simple Calculator4Null Safety
Intro to Null SafetyNullable vs Non-NullableThe ? and ! OperatorsLate Keyword & Null SafetyNull-Aware OperatorsNull Safety in ClassesRecap - User Profile System7Abstract Classes & Interfaces
Abstract ClassesAbstract MethodsInterfaces in DartImplicit InterfacesImplementing vs ExtendingMultiple InterfacesRecap - Shape Calculator10Collections & Generics
List, Set, Map OverviewType-Safe CollectionsGeneric ClassesGeneric MethodsGeneric ConstraintsIterable & IteratorRecap - Generic Storage13Advanced OOP Concepts
Composition vs InheritanceExtension MethodsCallable ClassesSealed Classes (Dart 3)Records (Dart 3)Patterns & Matching (3.0)Enums with Methods16Project: Library Management
Project OverviewBook and User Classes2Constructors in Dart
Default ConstructorNamed ConstructorsInitializer ListsConstant ConstructorsFactory ConstructorsRedirecting ConstructorsRecap - Shape Builder5Encapsulation
Public vs Private MembersThe _ Prefix ConventionLibrary-Level PrivacyGetters & Setters DepthInformation HidingRecap - Student Records8Mixins
Introduction to MixinsCreating MixinsUsing Multiple Mixinson Keyword in MixinsMixin vs InheritanceMixin vs InterfaceRecap - Animal System11Special Methods
toString() OverridehashCode & == OverrideComparable Interfacecall() MethodnoSuchMethod OverrideRecap - Custom Collection14Design Patterns Part 1
Intro to Design PatternsSingleton PatternFactory PatternObserver PatternStrategy Pattern3Class Properties
Instance vs Static MembersFinal & Const FieldsLate VariablesStatic Methods & FieldsGetters and SettersRecap - Bank Account Manager6Inheritance
Basic InheritanceThe super KeywordMethod OverridingThe @override AnnotationThe final Class KeywordConstructors & InheritanceRecap - Employee Hierarchy9Polymorphism
Polymorphism BasicsPolymorphism via InterfacesRuntime Type CheckingThe is & as OperatorsCovariant KeywordRecap - Payment Processor