Polymorphic Collections
Part of the Object Oriented Programming section of Coddy's GO journey — lesson 44 of 107.
One of the most powerful applications of interfaces is storing different types in the same collection. A slice of an interface type can hold any value that satisfies that interface, letting you group related but different types together.
Consider a scenario where you need to manage various shapes. Instead of separate slices for each type, you can use a single slice of the interface type:
type Shape interface {
Area() float64
}
type Circle struct{ Radius float64 }
func (c Circle) Area() float64 { return 3.14159 * c.Radius * c.Radius }
type Rectangle struct{ Width, Height float64 }
func (r Rectangle) Area() float64 { return r.Width * r.Height }
func main() {
shapes := []Shape{
Circle{Radius: 5},
Rectangle{Width: 4, Height: 3},
Circle{Radius: 2},
}
for _, s := range shapes {
fmt.Printf("Area: %.2f\n", s.Area())
}
}
The shapes slice holds both circles and rectangles. When iterating, each element responds to Area() according to its actual type. This is polymorphism applied to collections—the same loop handles all shape types uniformly.
Polymorphic collections are essential when building systems that process varied items: a notification system sending emails and SMS messages, a game updating different enemy types, or a document processor handling multiple file formats. The collection doesn't care about specific types—only that each element provides the required behavior.
Challenge
EasyLet's build a task management system that handles different types of tasks through a unified interface. You'll create various task types and process them all together in a polymorphic collection.
You'll organize your code across three files:
task.go: Define aTaskinterface that requires aSummary() stringmethod. This interface will be the common contract that all task types must fulfill.types.go: Create three different task types that each implement theTaskinterface:BugFixwithID(string) andSeverity(string) fields—itsSummary()returnsBug #[ID] ([Severity])FeaturewithName(string) andPoints(int) fields—itsSummary()returnsFeature: [Name] - [Points] ptsDocumentationwithTopic(string) field—itsSummary()returnsDocs: [Topic]
main.go: Create a function calledPrintBacklogthat accepts a slice ofTaskand prints each task's summary on its own line. Read task details from input, create one of each task type, collect them all into a single[]Taskslice, and pass it toPrintBacklog.
The following inputs will be provided:
- Line 1: Bug ID
- Line 2: Bug severity
- Line 3: Feature name
- Line 4: Feature points (integer)
- Line 5: Documentation topic
For example, given 1042, critical, Dark Mode, 8, and API Reference, your output should be:
Bug #1042 (critical)
Feature: Dark Mode - 8 pts
Docs: API ReferenceThe power here is that PrintBacklog doesn't need separate logic for bugs, features, or documentation—it simply iterates through the slice and calls Summary() on each element. Each task type responds with its own unique format, demonstrating how polymorphic collections let you process diverse types uniformly.
Cheat sheet
A slice of an interface type can hold any value that satisfies that interface, enabling polymorphic collections:
type Shape interface {
Area() float64
}
type Circle struct{ Radius float64 }
func (c Circle) Area() float64 { return 3.14159 * c.Radius * c.Radius }
type Rectangle struct{ Width, Height float64 }
func (r Rectangle) Area() float64 { return r.Width * r.Height }
func main() {
shapes := []Shape{
Circle{Radius: 5},
Rectangle{Width: 4, Height: 3},
Circle{Radius: 2},
}
for _, s := range shapes {
fmt.Printf("Area: %.2f\n", s.Area())
}
}
The shapes slice holds different types (Circle and Rectangle) that all implement the Shape interface. When iterating, each element responds to Area() according to its actual type—this is polymorphism applied to collections.
This pattern is useful for processing varied items uniformly: notification systems handling different message types, games updating various enemy types, or document processors handling multiple file formats.
Try it yourself
package main
import (
"fmt"
)
// TODO: Create a PrintBacklog function that accepts a slice of Task
// and prints each task's summary on its own line
func main() {
// Read input
var bugID string
var bugSeverity string
var featureName string
var featurePoints int
var docTopic string
fmt.Scanln(&bugID)
fmt.Scanln(&bugSeverity)
fmt.Scanln(&featureName)
fmt.Scanln(&featurePoints)
fmt.Scanln(&docTopic)
// TODO: Create one of each task type (BugFix, Feature, Documentation)
// TODO: Collect all tasks into a single []Task slice
// TODO: Call PrintBacklog with the slice of tasks
}
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 Parser3Pointers & 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