Composite Pattern
Part of the Object Oriented Programming section of Coddy's Dart journey — lesson 99 of 110.
The Composite pattern lets you treat individual objects and groups of objects uniformly. It composes objects into tree structures where both single elements and collections of elements share the same interface. This is perfect for representing hierarchies like file systems, organization charts, or UI components.
The pattern has two types of participants: Leaf nodes (individual objects with no children) and Composite nodes (containers that hold other components). Both implement a common Component interface:
abstract class FileSystemItem {
String get name;
int getSize();
void display([String indent = '']);
}
class File implements FileSystemItem {
@override
final String name;
final int size;
File(this.name, this.size);
@override
int getSize() => size;
@override
void display([String indent = '']) {
print('$indent- $name ($size KB)');
}
}
class Folder implements FileSystemItem {
@override
final String name;
final List<FileSystemItem> _children = [];
Folder(this.name);
void add(FileSystemItem item) => _children.add(item);
@override
int getSize() => _children.fold(0, (sum, item) => sum + item.getSize());
@override
void display([String indent = '']) {
print('$indent+ $name/');
for (var child in _children) {
child.display('$indent ');
}
}
}
void main() {
var docs = Folder('Documents');
docs.add(File('resume.pdf', 150));
docs.add(File('photo.jpg', 2400));
var projects = Folder('Projects');
projects.add(File('main.dart', 25));
docs.add(projects);
docs.display();
print('Total: ${docs.getSize()} KB');
}The Folder class can contain both File objects and other Folder objects. When you call getSize() on a folder, it recursively calculates the total size of all its contents. Client code doesn't need to know whether it's working with a single file or an entire folder hierarchy - both respond to the same methods.
Challenge
EasyLet's build an organization chart system using the Composite pattern! You'll create a structure where both individual employees and departments (which contain employees and other departments) can be treated uniformly - perfect for representing company hierarchies.
You'll organize your code into two files:
organization.dart: This file contains your component interface and both leaf and composite classes. Create an abstract classOrganizationUnitwith anamegetter, agetSalaryBudget()method that returns anint, and adisplay([String indent = ''])method for showing the hierarchy. Build anEmployeeclass (the leaf) that implementsOrganizationUnit. It should have a name and salary, withgetSalaryBudget()returning the employee's salary. Itsdisplay()should print[indent]- [name] ($[salary]). Then create aDepartmentclass (the composite) that also implementsOrganizationUnit. It should have a name and maintain a list ofOrganizationUnitchildren. Add anadd()method to include employees or sub-departments. ItsgetSalaryBudget()should recursively sum all children's budgets. Itsdisplay()should print[indent]+ [name]/followed by displaying each child with increased indentation (add two spaces).main.dart: Import your organization file and build a company structure. Create anEngineeringdepartment containing two employees:Alicewith salary80000andBobwith salary75000. Create aQAdepartment with one employee:Carolwith salary65000. Now create aTechnologydepartment and add bothEngineeringandQAas sub-departments. Calldisplay()on the Technology department, then printTotal Budget: $followed by the total salary budget.
The beauty of the Composite pattern is that Technology.getSalaryBudget() automatically calculates the total across all nested departments and employees - the client code doesn't need to know the internal structure!
Expected output:
+ Technology/
+ Engineering/
- Alice ($80000)
- Bob ($75000)
+ QA/
- Carol ($65000)
Total Budget: $220000Cheat sheet
The Composite pattern allows you to treat individual objects and groups of objects uniformly by composing them into tree structures where both single elements and collections share the same interface.
The pattern consists of:
- Component: An abstract interface defining common operations
- Leaf: Individual objects with no children
- Composite: Containers that hold other components (leaves or composites)
Example: File System
abstract class FileSystemItem {
String get name;
int getSize();
void display([String indent = '']);
}
class File implements FileSystemItem {
@override
final String name;
final int size;
File(this.name, this.size);
@override
int getSize() => size;
@override
void display([String indent = '']) {
print('$indent- $name ($size KB)');
}
}
class Folder implements FileSystemItem {
@override
final String name;
final List<FileSystemItem> _children = [];
Folder(this.name);
void add(FileSystemItem item) => _children.add(item);
@override
int getSize() => _children.fold(0, (sum, item) => sum + item.getSize());
@override
void display([String indent = '']) {
print('$indent+ $name/');
for (var child in _children) {
child.display('$indent ');
}
}
}
void main() {
var docs = Folder('Documents');
docs.add(File('resume.pdf', 150));
docs.add(File('photo.jpg', 2400));
var projects = Folder('Projects');
projects.add(File('main.dart', 25));
docs.add(projects);
docs.display();
print('Total: ${docs.getSize()} KB');
}The Folder class can contain both File objects and other Folder objects. When you call getSize() on a folder, it recursively calculates the total size of all its contents. Client code treats individual files and entire folder hierarchies uniformly.
Try it yourself
import 'organization.dart';
void main() {
// TODO: Create an Engineering department
// TODO: Add Alice with salary 80000
// TODO: Add Bob with salary 75000
// TODO: Create a QA department
// TODO: Add Carol with salary 65000
// TODO: Create a Technology department
// TODO: Add Engineering and QA as sub-departments
// TODO: Call display() on the Technology department
// TODO: Print "Total Budget: $" followed by the total salary budget
}
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 Processor12Async OOP
Futures & async/awaitStreams BasicsStream ControllersAsync ConstructorsAsync in Class MethodsRecap - Data Fetcher15Design Patterns Part 2
Command PatternAdapter PatternDecorator PatternTemplate Method PatternState PatternComposite PatternRepository Pattern