Composite Pattern
Part of the Object Oriented Programming section of Coddy's GO journey — lesson 96 of 107.
The Composite pattern lets you treat individual objects and groups of objects uniformly. While State changes behavior based on internal state, Composite builds tree structures where both leaves and containers share the same interface.
This pattern is perfect for hierarchical structures like file systems, organization charts, or UI components. In Go, we define a common interface that both individual items and containers implement:
type Component interface {
GetSize() int
}
type File struct {
Name string
Size int
}
func (f File) GetSize() int {
return f.Size
}The composite (container) holds children and implements the same interface by aggregating their results:
type Folder struct {
Name string
Children []Component
}
func (f *Folder) Add(c Component) {
f.Children = append(f.Children, c)
}
func (f Folder) GetSize() int {
total := 0
for _, child := range f.Children {
total += child.GetSize()
}
return total
}Now you can nest folders within folders, and calling GetSize() works identically whether it's a single file or an entire directory tree:
docs := &Folder{Name: "docs"}
docs.Add(File{Name: "readme.txt", Size: 100})
docs.Add(File{Name: "notes.txt", Size: 50})
root := &Folder{Name: "root"}
root.Add(docs)
root.Add(File{Name: "config.json", Size: 25})
fmt.Println(root.GetSize()) // 175Composite is ideal when you need to represent part-whole hierarchies and want clients to treat individual objects and compositions identically.
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 or other departments) can be treated uniformly—perfect for calculating total salaries across any part of the organization.
You'll organize your code across three files:
component.go: Define the common interface that both individuals and groups will implement.Create an
OrgComponentinterface with two methods:GetSalary() int— returns the total salary for this componentGetName() string— returns the name of the employee or department
organization.go: Implement both the leaf (individual employee) and composite (department) types.Create an
Employeestruct withName(string) andSalary(int) fields. ItsGetSalary()returns its own salary, andGetName()returns its name.Create a
Departmentstruct with aName(string) field and aMembersslice that holdsOrgComponentitems. Add anAdd(c OrgComponent)method to add members. ItsGetSalary()should return the sum of all members' salaries, andGetName()returns the department name.main.go: Build an organization structure and calculate salaries.Read the number of employees. For each employee, read their name and salary, creating
Employeeinstances. Then read the number of departments. For each department, read its name and the count of member indices, followed by those indices (0-based, referring to previously created employees or departments in order of creation).After building the structure, read a final index and print that component's name and total salary in the format:
[name]: [salary]
The following inputs will be provided:
- Number of employees, then each employee's name and salary on separate lines
- Number of departments, then each department's name, member count, and member indices
- Final index to query
For example, given:
3
Alice
50000
Bob
60000
Carol
55000
1
Engineering
3
0
1
2
3Your output should be:
Engineering: 165000And given:
4
Alice
50000
Bob
60000
Carol
55000
Dave
70000
2
Dev
2
0
1
QA
2
2
3
4Your output should be:
QA: 125000And given:
2
Alice
50000
Bob
60000
0
1Your output should be:
Bob: 60000Notice how GetSalary() works identically whether you call it on a single employee or an entire department—the Composite pattern lets you treat individuals and groups uniformly through the same interface!
Cheat sheet
The Composite pattern treats individual objects and groups of objects uniformly through a shared interface. It's ideal for hierarchical structures like file systems, organization charts, or UI components.
Define a common interface that both individual items and containers implement:
type Component interface {
GetSize() int
}Implement the leaf (individual item):
type File struct {
Name string
Size int
}
func (f File) GetSize() int {
return f.Size
}Implement the composite (container) that holds children and aggregates their results:
type Folder struct {
Name string
Children []Component
}
func (f *Folder) Add(c Component) {
f.Children = append(f.Children, c)
}
func (f Folder) GetSize() int {
total := 0
for _, child := range f.Children {
total += child.GetSize()
}
return total
}Usage example showing uniform treatment of individual items and compositions:
docs := &Folder{Name: "docs"}
docs.Add(File{Name: "readme.txt", Size: 100})
docs.Add(File{Name: "notes.txt", Size: 50})
root := &Folder{Name: "root"}
root.Add(docs)
root.Add(File{Name: "config.json", Size: 25})
fmt.Println(root.GetSize()) // 175Try it yourself
package main
import "fmt"
func main() {
// Read number of employees
var numEmployees int
fmt.Scanln(&numEmployees)
// Store all components (employees and departments) in order of creation
var components []OrgComponent
// TODO: Read each employee's name and salary
// Create Employee instances and add them to components slice
for i := 0; i < numEmployees; i++ {
var name string
var salary int
fmt.Scanln(&name)
fmt.Scanln(&salary)
// TODO: Create employee and add to components
}
// Read number of departments
var numDepartments int
fmt.Scanln(&numDepartments)
// TODO: Read each department's name, member count, and member indices
// Create Department instances, add members by index, and add to components
for i := 0; i < numDepartments; i++ {
var deptName string
var memberCount int
fmt.Scanln(&deptName)
fmt.Scanln(&memberCount)
// TODO: Create department, read member indices, add members, add to components
}
// Read the final index to query
var queryIndex int
fmt.Scanln(&queryIndex)
// TODO: Print the component's name and total salary in format: [name]: [salary]
// Example: fmt.Printf("%s: %d\n", name, salary)
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of Go OOP
External FilesGo Workspace & ModulesPackages & ImportsExported vs Unexported NamesIntroduction to OOP in GoStructs as ClassesDefining Methods on StructsPointer vs Value ReceiversStruct InitializationConstructor FunctionsRecap - Simple Calculator4Interfaces
Introduction to InterfacesImplicit ImplementationInterface as ContractEmpty Interface (any)Type AssertionType SwitchInterface CompositionStringer & Error InterfacesRecap - Shape Calculator7Encapsulation
Exported vs Unexported FieldsPackage-Level EncapsulationGetter & Setter MethodsInformation Hiding in GoRecap - Student Records10Generics (Go 1.18+)
Introduction to GenericsType ParametersType ConstraintsGeneric StructsGeneric Methods WorkaroundRecap - Generic Collection2Types & Structs Deep Dive
Basic & Composite TypesCustom Type DefinitionsStruct TagsAnonymous StructsNested StructsZero Values & DefaultsRecap - Contact Book5Composition Over Inheritance
Why Go Has No InheritanceStruct Embedding BasicsMethod PromotionEmbedding Multiple StructsEmbedding vs AggregationShadowing Embedded MethodsRecap - Employee Hierarchy8Error Handling & OOP
The error InterfaceCustom Error TypesError Wrapping (fmt.Errorf)Sentinel Errorserrors.Is() and errors.As()Panic, Defer, and RecoverRecap - File Parser11Standard Library & OOP
io.Reader & io.Writersort.Interfacefmt.Stringer Interfaceencoding/json with Structshttp.Handler InterfaceRecap - REST API Models14Design Patterns Part 2
Command PatternAdapter PatternDecorator PatternTemplate Method PatternState PatternComposite PatternMiddleware as Decorator3Pointers & Memory
Pointer Basics in GoPointers to StructsPass by Value vs ReferenceThe new() FunctionGarbage Collection in GoRecap - Linked List Builder6Polymorphism in Go
Polymorphism via InterfacesDuck Typing in GoInterface Satisfaction RulesPolymorphic CollectionsDependency InjectionRecap - Payment Processor9Concurrency & OOP
Goroutines BasicsChannels & CommunicationBuffered vs Unbuffered ChanSelect Statementsync.Mutex & sync.RWMutexsync.WaitGroupThread-Safe Struct DesignRecap - Worker Pool