Intro to Design Patterns
Part of the Object Oriented Programming section of Coddy's GO journey — lesson 85 of 107.
Design patterns are reusable solutions to common problems that arise in software design. They aren't code you copy directly, but rather templates that guide how you structure your types and their interactions to solve specific challenges.
Design patterns are typically grouped into three categories:
| Category | Purpose | Examples |
|---|---|---|
| Creational | Object creation mechanisms | Singleton, Factory, Builder |
| Structural | Composing types into larger structures | Adapter, Decorator, Composite |
| Behavioral | Communication between objects | Observer, Strategy, Command |
In Go, design patterns look different than in traditional OOP languages.
Without classes or inheritance, Go implements patterns using interfaces, struct embedding, and composition. For example, where Java might use an abstract class, Go uses an interface. Where C++ might use inheritance, Go uses embedding.
Learning patterns helps you recognize common problems and communicate solutions effectively with other developers. When someone mentions "use the Strategy pattern here," the entire team understands the approach without lengthy explanations.
Over the next lessons, you'll implement several fundamental patterns in Go, starting with creational patterns like Singleton and Factory, then moving to behavioral patterns like Observer and Strategy.
Challenge
EasyLet's build a simple notification system that demonstrates how design patterns help organize code! You'll create a structure that could easily be extended with patterns like Observer or Strategy in future lessons—but for now, focus on setting up clean interfaces and types that follow good OOP principles.
You'll organize your code across three files:
notifier.go: Define the core interface and types for your notification system.Create a
Notifierinterface with a single methodSend(message string) stringthat returns a confirmation message.Implement two concrete notifier types:
EmailNotifierwith anAddressfield—itsSendmethod returnsEmail to [address]: [message]SMSNotifierwith aPhoneNumberfield—itsSendmethod returnsSMS to [phone]: [message]
dispatcher.go: Create a dispatcher that works with any notifier through the interface.Build a
NotificationDispatcherstruct that holds a slice ofNotifierinterfaces. Add these methods:AddNotifier(n Notifier)to register a notifierBroadcast(message string) []stringthat sends the message through all registered notifiers and returns a slice of all confirmation strings
Also create a
NewNotificationDispatcher()constructor that returns an initialized dispatcher.main.go: Set up notifiers and broadcast messages.Read a count of notifiers to create. For each notifier, read its type (
emailorsms) and its destination (address or phone number). Add each to a dispatcher.Then read a message to broadcast. Send it through all notifiers and print each confirmation on its own line.
The following inputs will be provided:
- Line 1: Number of notifiers
- For each notifier: type (
emailorsms), then destination - Final line: Message to broadcast
For example, given:
3
email
alice@example.com
sms
555-1234
email
bob@example.com
Server maintenance at midnightYour output should be:
Email to alice@example.com: Server maintenance at midnight
SMS to 555-1234: Server maintenance at midnight
Email to bob@example.com: Server maintenance at midnightAnd given:
2
sms
555-9999
sms
555-0000
Meeting canceledYour output should be:
SMS to 555-9999: Meeting canceled
SMS to 555-0000: Meeting canceledThis structure demonstrates how interfaces enable flexible, extensible designs. The dispatcher doesn't know about specific notifier types—it just knows they can Send. This is the foundation that patterns like Observer and Strategy build upon!
Cheat sheet
Design patterns are reusable solutions to common problems in software design. They are templates that guide how to structure types and their interactions.
Design patterns are grouped into three categories:
| Category | Purpose | Examples |
|---|---|---|
| Creational | Object creation mechanisms | Singleton, Factory, Builder |
| Structural | Composing types into larger structures | Adapter, Decorator, Composite |
| Behavioral | Communication between objects | Observer, Strategy, Command |
In Go, design patterns are implemented using interfaces, struct embedding, and composition instead of classes and inheritance. Where traditional OOP languages use abstract classes, Go uses interfaces. Where they use inheritance, Go uses embedding.
Try it yourself
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Read the number of notifiers
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
// Create a new dispatcher
dispatcher := NewNotificationDispatcher()
// Read each notifier and add to dispatcher
for i := 0; i < count; i++ {
notifierType, _ := reader.ReadString('\n')
notifierType = strings.TrimSpace(notifierType)
destination, _ := reader.ReadString('\n')
destination = strings.TrimSpace(destination)
// TODO: Create the appropriate notifier based on type
// and add it to the dispatcher
// Hint: Check if notifierType is "email" or "sms"
}
// Read the message to broadcast
message, _ := reader.ReadString('\n')
message = strings.TrimSpace(message)
// TODO: Broadcast the message and print each confirmation
}
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 Collection13Design Patterns Part 1
Intro to Design PatternsSingleton PatternFactory PatternAbstract Factory PatternObserver PatternStrategy Pattern2Types & 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