LEARN DESIGN PATTERNS
Learn Design Patterns: From Novice to Software Architect
Goal: Deeply understand software design patterns—from the fundamental “why” to building complex, maintainable, and elegant systems that leverage these proven solutions.
Why Learn Design Patterns?
Design patterns are the collected wisdom of thousands of brilliant software engineers. They are not specific algorithms or libraries, but rather high-level, reusable solutions to commonly occurring problems within a given context in software design. Most developers treat them as academic concepts, but true mastery of patterns transforms you from a coder into an architect.
After completing these projects, you will:
- Speak a common language with other developers, making collaboration more efficient.
- Build systems that are more flexible, reusable, and easier to maintain.
- Recognize common problems and immediately know a set of proven ways to solve them.
- Understand the trade-offs between different solutions.
- Write code that is elegant, professional, and robust.
Core Concept Analysis
The Three Families of Patterns (Gang of Four)
The most famous catalog of patterns comes from the book “Design Patterns: Elements of Reusable Object-Oriented Software” by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (the “Gang of Four”). They are organized into three families.
┌─────────────────────────────────────────────────────────────────────────┐
│ DESIGN PATTERNS │
│ │
├──────────────────────┬───────────────────────┬──────────────────────────┤
│ CREATIONAL │ STRUCTURAL │ BEHAVIORAL │
│ (How to create objs) │ (How to compose objs) │ (How objs communicate) │
├──────────────────────┼───────────────────────┼──────────────────────────┤
│ • Singleton │ • Adapter │ • Observer │
│ • Factory Method │ • Decorator │ • Strategy │
│ • Abstract Factory │ • Composite │ • Command │
│ • Builder │ • Facade │ • Iterator │
│ • Prototype │ • Proxy │ • State │
│ │ • Bridge │ • Template Method │
│ │ • Flyweight │ • Chain of Responsibility│
│ │ │ • Visitor, Memento, │
│ │ │ Mediator, Interpreter │
└──────────────────────┴───────────────────────┴──────────────────────────┘
Key Design Principles (The “Why” Behind the Patterns)
Patterns are manifestations of deeper principles. Understanding these principles is more important than memorizing patterns.
| Principle | Description | Example Pattern |
|---|---|---|
| Encapsulate What Varies | Find the parts of your code that change and separate them from what stays the same. | Strategy |
| Program to an Interface… | …not an Implementation. Depend on abstractions, not concrete classes. | Factory Method |
| Favor Composition… | …over Inheritance. Build complex behavior by assembling objects, not from a rigid class hierarchy. | Decorator |
| Strive for Loose Coupling | Minimize dependencies between objects. Changes in one shouldn’t break others. | Observer |
| Open-Closed Principle | Classes should be open for extension, but closed for modification. | Decorator |
| Dependency Inversion | High-level modules should not depend on low-level ones. Both should depend on abstractions. | Abstract Factory |
Project List
The following 12 projects will guide you from basic creational patterns to complex behavioral systems, teaching you to think in patterns.
Project 1: Application Configuration Manager
- File: LEARN_DESIGN_PATTERNS.md
- Main Programming Language: Java
- Alternative Programming Languages: C#, Python, C++
- Coolness Level: Level 1: Pure Corporate Snoozefest
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 1: Beginner
- Knowledge Area: Object Creation / Global State Management
- Software or Tool: A simple console application
- Main Book: “Head First Design Patterns” by Eric Freeman & Elisabeth Robson
What you’ll build: A thread-safe Configuration class that loads settings from a .properties or .json file and provides global access to them, ensuring only one instance of the manager ever exists.
Why it teaches design patterns: This is the classic, canonical example for the Singleton pattern. It forces you to think about instance control, global access points, and the potential pitfalls of shared state, such as thread safety.
Core challenges you’ll face:
- Ensuring a single instance → maps to private constructors and static access methods
- Lazy vs. eager instantiation → maps to performance and startup time trade-offs
- Thread safety → maps to double-checked locking or using an initialization-on-demand holder
- Reading from a file → maps to basic file I/O and parsing
Key Concepts:
- Singleton Pattern: “Head First Design Patterns” Chapter 5
- Static vs. Instance Members: Java documentation on class members
- Thread Safety: “Effective Java” by Joshua Bloch, Item 83
Difficulty: Beginner Time estimate: A few hours Prerequisites: Basic knowledge of a class-based language (Java, C#).
Real world outcome:
$ java -jar config-app.jar
Loading configuration from 'app.properties'...
Configuration loaded.
Database URL: jdbc:mysql://localhost:3306/prod
API Key: a1b2c3d4-e5f6-7890-ghij-klmnopqrstuv
Max Connections: 10
# Running again shows it doesn't reload
$ java -jar config-app.jar
Using existing configuration instance.
Database URL: jdbc:mysql://localhost:3306/prod
...
Implementation Hints:
- Make the constructor
privateto prevent direct instantiation withnew. - Create a
private staticfield to hold the single instance. - Create a
public staticmethod (e.g.,getInstance()) that acts as the global access point. - Inside
getInstance(), check if the instance isnull. If it is, create it. Then, always return the instance. - Consider thread safety: what happens if two threads call
getInstance()at the same time when the instance is null? You might need to usesynchronizedor a more advanced technique.
Learning milestones:
- Basic Singleton works → You understand instance control.
- Configuration loads from a file → The singleton has a real-world purpose.
- It is thread-safe → You understand concurrent access to a singleton.
Project 2: Document Converter Tool
- File: LEARN_DESIGN_PATTERNS.md
- Main Programming Language: Java
- Alternative Programming Languages: Python, C#, Go
- Coolness Level: Level 2: Practical but Forgettable
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 2: Intermediate
- Knowledge Area: Creational Patterns / Behavioral Patterns
- Software or Tool: Command-line application
- Main Book: “Design Patterns: Elements of Reusable Object-Oriented Software” by GoF
What you’ll build: A command-line tool that takes a document (e.g., Markdown) and converts it to different formats (HTML, Plain Text, LaTeX) by using different conversion algorithms interchangeably.
Why it teaches design patterns: It combines two powerful patterns. The Factory Method lets subclasses decide which converter object to create, decoupling your main logic from concrete converter classes. The Strategy pattern lets you easily switch out the conversion algorithm at runtime.
Core challenges you’ll face:
- Decoupling creator from product → maps to using an abstract
ConverterFactory - Defining a family of algorithms → maps to creating a common
ConversionStrategyinterface - Encapsulating algorithms → maps to implementing concrete strategies like
HtmlConversionStrategy - Switching strategies at runtime → maps to passing the desired strategy to a context object
Key Concepts:
- Factory Method Pattern: Refactoring.Guru - Factory Method
- Strategy Pattern: “Head First Design Patterns” Chapter 1
- Program to an Interface: “Head First Design Patterns” Chapter 1
Difficulty: Intermediate Time estimate: Weekend Prerequisites: Project 1, understanding of interfaces and polymorphism.
Real world outcome:
$ java -jar converter.jar my_doc.md --to=html
Creating HTML converter...
Applying HTML conversion strategy...
Output written to 'my_doc.html'.
$ java -jar converter.jar my_doc.md --to=text
Creating Text converter...
Applying Text conversion strategy...
Output written to 'my_doc.txt'.
Implementation Hints:
- Strategy: Define an interface
ConversionStrategywith a methodconvert(String document). Create concrete classes likeHtmlStrategy,TextStrategy. - Context: Create a
DocumentConverterclass that holds a reference to aConversionStrategy. ItsperformConversion()method will delegate the work to the strategy object. - Factory Method: Create an abstract
ConverterFactoryclass with a methodcreateConverter(). Concrete factories likeHtmlConverterFactorywill implement this method to return aDocumentConverterpre-configured with anHtmlStrategy. - Your
mainmethod will select the appropriate factory based on command-line arguments, use it to create a converter, and then run the conversion.
Learning milestones:
- Strategies can be swapped → You understand encapsulating algorithms.
- Factories create configured objects → You understand decoupling object creation.
- The system is extensible → You can add a new format (e.g., XML) by just adding a new
StrategyandFactory, without touching existing code.
Project 3: A Build-Your-Own Pizza API
- File: LEARN_DESIGN_PATTERNS.md
- Main Programming Language: C#
- Alternative Programming Languages: Java, Python, TypeScript
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 2: Intermediate
- Knowledge Area: Creational Patterns / Structural Patterns
- Software or Tool: A simple web API or console app.
- Main Book: “Head First Design Patterns”
What you’ll build: An API for ordering a pizza where you can construct a complex Pizza object step-by-step (crust, sauce, size) and then add toppings (cheese, pepperoni, onions) dynamically.
Why it teaches design patterns: A perfect combination of Builder and Decorator. The Builder pattern simplifies the creation of a complex object with many optional parts, avoiding a “telescoping constructor”. The Decorator pattern allows you to add responsibilities (toppings) to an object (the pizza) dynamically and transparently.
Core challenges you’ll face:
- Constructing a complex object → maps to creating a
PizzaBuilderwith methods likesetCrust(),setSauce() - Separating construction from representation → maps to the builder produces the final
Pizzaobject only at the end - Adding behavior dynamically → maps to wrapping a
Pizzaobject in one or moreToppingDecoratorobjects - Maintaining a common interface → maps to decorators and the base pizza sharing the same interface (e.g.,
getCost(),getDescription())
Key Concepts:
- Builder Pattern: “Effective Java” by Joshua Bloch, Item 2
- Decorator Pattern: “Head First Design Patterns” Chapter 3
- Composition over Inheritance: Core principle demonstrated by the Decorator pattern.
Difficulty: Intermediate Time estimate: Weekend Prerequisites: Understanding of classes, interfaces, and composition.
Real world outcome: A console app that produces this output:
// Code:
Pizza pizza = new PizzaBuilder("Large")
.withCrust("Thin")
.withSauce("Tomato")
.build();
pizza = new Cheese(pizza);
pizza = new Pepperoni(pizza);
System.out.println(pizza.getDescription() + " $" + pizza.getCost());
// Output:
Large Thin Crust Pizza with Tomato Sauce, Cheese, Pepperoni $14.50
Implementation Hints:
- Pizza Class: Make the
Pizzaclass have a constructor that takes aPizzaBuilder. Its properties (crust, sauce) should be private. - Builder: Create a nested static
PizzaBuilderclass insidePizza. It will have methods likewithCrust(),withSauce(), and a finalbuild()method that creates thePizzainstance. - Decorator Base: Create an abstract
ToppingDecoratorclass that implements the same interface asPizza(e.g.,IPizza). It should hold a reference to theIPizzaobject it wraps. - Concrete Decorators: Create classes like
CheeseandPepperonithat extendToppingDecorator. TheirgetCost()methods should call the wrapped pizza’sgetCost()and add their own cost. TheirgetDescription()should do the same.
Learning milestones:
- Complex
Pizzaobjects are created easily → You’ve mastered the Builder pattern. - Toppings can be added in any order → You’ve mastered the Decorator pattern.
- The final cost and description are calculated correctly → You understand how decorators chain calls.
Project 4: A Mini GUI Framework
- File: LEARN_DESIGN_PATTERNS.md
- Main Programming Language: Python
- Alternative Programming Languages: Java, C#
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 3: Advanced
- Knowledge Area: Structural Patterns / Behavioral Patterns
- Software or Tool: A simple graphical library (like Tkinter or Pygame) or just console output.
- Main Book: “Design Patterns: Elements of Reusable Object-Oriented Software” by GoF
What you’ll build: A simple framework for building a user interface where UI elements (Panels, Buttons, TextFields) can be nested inside each other, and elements can react to events like “click”.
Why it teaches design patterns: This is the textbook application of the Composite and Observer patterns. Composite allows you to treat a single Button and a Panel full of buttons in the same way (a part-whole hierarchy). Observer allows UI elements to subscribe to events from other elements without being tightly coupled.
Core challenges you’ll face:
- Treating individual objects and compositions uniformly → maps to a shared
Componentinterface for both leaf nodes (Button) and composites (Panel) - Building tree structures → maps to a
Panelclass withadd()andremove()methods for child components - Notifying objects of state changes → maps to a
Subject(the button) that notifies registeredObservers(listeners) - Decoupling the sender from the receiver → maps to listeners implementing an
Observerinterface without the button knowing their concrete types
Key Concepts:
- Composite Pattern: “Head First Design Patterns” Chapter 9
- Observer Pattern: “Head First Design Patterns” Chapter 2
- Part-Whole Hierarchies: The core concept of the Composite pattern.
Difficulty: Advanced Time estimate: 1-2 weeks Prerequisites: Project 2, solid understanding of object composition and events.
Real world outcome: A simple application that draws a UI tree to the console and handles events.
# Code:
root = Panel("RootPanel")
panel1 = Panel("SubPanel1")
button1 = Button("OK")
button2 = Button("Cancel")
# Listeners
button1.add_observer(lambda: print("OK button was clicked!"))
button2.add_observer(lambda: print("Cancel button was clicked!"))
root.add(panel1)
panel1.add(button1)
root.add(button2)
root.draw()
# Simulating a click
button1.click()
# Console Output:
--- Panel: RootPanel ---
--- Panel: SubPanel1 ---
[Button: OK]
--- End Panel: SubPanel1 ---
[Button: Cancel]
--- End Panel: RootPanel ---
OK button was clicked!
Implementation Hints:
- Composite: Define a
Componentinterface with methods likedraw()andclick(). Create aButton(leaf) class and aPanel(composite) class that both implementComponent. ThePanel’sdraw()method should iterate and calldraw()on its children. - Observer: Create an
Observerinterface with a methodupdate(). Create aSubjectclass (or have theButtonimplement it) withadd_observer(),remove_observer(), andnotify_observers()methods. - When a button’s
click()method is called, it should in turn callnotify_observers(), which loops through its registered observers and calls theirupdate()method.
Learning milestones:
- You can build nested UI structures → You understand the Composite pattern.
- Clicking a button triggers an action in a separate object → You understand the Observer pattern.
- The code is clean and scalable → You can add new components and listeners without major refactoring.
Project 5: Smart Home Hub
- File: LEARN_DESIGN_PATTERNS.md
- Main Programming Language: Python
- Alternative Programming Languages: Java, C#, Go
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 2: Intermediate
- Knowledge Area: Structural Patterns
- Software or Tool: Console application simulating smart devices.
- Main Book: “Design Patterns: Elements of Reusable Object-Oriented Software” by GoF
What you’ll build: A central hub that controls a variety of smart home devices (lights, thermostats, coffee machines), some of which have incompatible interfaces. The hub will also provide simple “scene” commands like “Wake Up” or “Movie Night”.
Why it teaches design patterns: This project demonstrates two key structural patterns. The Adapter pattern is used to make incompatible device interfaces work with your standard hub interface. The Facade pattern provides a simple, unified interface to a complex subsystem (e.g., the sequence of calls needed for “Movie Night”).
Core challenges you’ll face:
- Integrating incompatible interfaces → maps to creating an adapter class that translates calls, e.g.,
NewLightAdapterfor aLegacyLight - Simplifying a complex subsystem → maps to a
HomeTheaterFacadewith awatchMovie()method that calls multiple underlying device methods - Providing a consistent client experience → maps to the hub only interacting with its own standard
ISmartDeviceinterface
Key Concepts:
- Adapter Pattern: “Head First Design Patterns” Chapter 7
- Facade Pattern: “Head First Design Patterns” Chapter 7
- Principle of Least Knowledge: A key idea behind the Facade pattern.
Difficulty: Intermediate Time estimate: Weekend Prerequisites: Understanding of interfaces and composition.
Real world outcome:
$ python hub.py movie_night
Initializing Movie Night scene...
- Dimming lights to 20%
- Lowering thermostat to 68 degrees
- Turning on TV
- Setting input to HDMI 2
Movie Night scene is active.
$ python hub.py control light1 on
Turning light1 on.
Implementation Hints:
- Define a standard interface
ISmartDevicewith methods liketurn_on(),turn_off(). - Create concrete device classes like
SmartLightthat implement this interface. - Create a “legacy” or “third-party” device class, e.g.,
AcmeThermostat, with a different interface (e.g.,set_temperature_f()). - Adapter: Create an
AcmeThermostatAdapterthat implementsISmartDevice. Its methods will internally call the corresponding methods on theAcmeThermostatobject it wraps. - Facade: Create a
HomeFacadeclass. Give it a methodstart_movie_night(). This method will hold references to all the necessary devices and call their methods in the correct sequence.
Learning milestones:
- You can control all devices through a single interface → You’ve mastered the Adapter pattern.
- Complex scenes are triggered by a single command → You’ve mastered the Facade pattern.
- The system is easy to extend with new devices and scenes → You understand how these patterns improve maintainability.
Project 6: A Text Editor with Undo/Redo
- File: LEARN_DESIGN_PATTERNS.md
- Main Programming Language: Java
- Alternative Programming Languages: C#, C++, Python
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 3: Advanced
- Knowledge Area: Behavioral Patterns
- Software or Tool: Console-based text editor.
- Main Book: “Design Patterns: Elements of Reusable Object-Oriented Software” by GoF
What you’ll build: A simple console application that acts as a text editor. You can type text, and crucially, you can issue “undo” and “redo” commands to navigate the history of your changes.
Why it teaches design patterns: This is the ultimate project for the Command and Memento patterns. Command encapsulates each action (like “insert character” or “delete word”) into an object. Memento provides the ability to save and restore the state of the document, allowing the commands to perform their undo/redo operations.
Core challenges you’ll face:
- Encapsulating requests as objects → maps to creating an
ICommandinterface withexecute()andundo()methods - Supporting undoable operations → maps to the
undo()method reversing theexecute()action - Saving and restoring an object’s state → maps to a
Documentcreating aDocumentMementoobject that stores its content - Managing a history of commands → maps to using two stacks, one for undo history and one for redo history
Key Concepts:
- Command Pattern: “Head First Design Patterns” Chapter 6
- Memento Pattern: Refactoring.Guru - Memento
- State Management: The core problem solved by Memento.
Difficulty: Advanced Time estimate: 1-2 weeks Prerequisites: Project 2, comfortable with data structures like Stacks.
Real world outcome: A running console application:
> type Hello
Document: 'Hello'
> type World
Document: 'Hello World'
> undo
Document: 'Hello'
> undo
Document: ''
> redo
Document: 'Hello'
> redo
Document: 'Hello World'
> quit
Implementation Hints:
- Document Class (Originator): This class holds the text content. It will have a method
createMemento()that saves its current state to aMementoobject, and arestore(Memento m)method to set its state from a memento. - Memento Class: A simple object that stores the state of the document (e.g., the text string). It should have a
getState()method but no public setter. - Command Interface:
ICommandwithexecute()andundo()methods. - Concrete Command: e.g.,
TypeCommand. Its constructor takes the text to add.execute()appends the text.undo()removes it. The command should capture aMementoof the document’s state before it executes. Theundomethod uses that memento to restore the state. - Invoker: A
Historyclass that holds two stacks:undoStackandredoStack. When a command is executed, it’s pushed onto theundoStack. When “undo” is called, it pops a command, calls itsundo()method, and pushes it to theredoStack.
Learning milestones:
- Actions are encapsulated in command objects → You understand the Command pattern.
- You can save and restore the document’s state → You understand the Memento pattern.
- A full undo/redo history works perfectly → You can orchestrate multiple patterns to solve a complex problem.
Project 7: A File System Analyzer
- File: LEARN_DESIGN_PATTERNS.md
- Main Programming Language: Python
- Alternative Programming Languages: Java, C#, Go
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 3: Advanced
- Knowledge Area: Behavioral Patterns / Structural Patterns
- Software or Tool: Command-line tool that operates on the local filesystem.
- Main Book: “Design Patterns: Elements of Reusable Object-Oriented Software” by GoF
What you’ll build: A tool that traverses a directory tree and performs various operations on the files and folders, such as calculating total size, finding the largest file, or generating a tree-view report, without changing the file/folder classes.
Why it teaches design patterns: This project elegantly combines the Composite, Iterator, and Visitor patterns. Composite models the file/directory tree structure. Iterator provides a way to traverse this structure. Visitor lets you define new operations on the structure without modifying the core file/directory objects.
Core challenges you’ll face:
- Modeling a tree structure → maps to the Composite pattern, with
Fileas a leaf andDirectoryas a composite - Defining new operations without changing classes → maps to the core purpose of the Visitor pattern
- Separating an algorithm from an object structure → maps to having
SizeVisitorandSearchVisitoroperate on the same file/directory objects - Traversing the object structure → maps to having each component’s
accept(Visitor v)method responsible for traversal
Key Concepts:
- Visitor Pattern: Refactoring.Guru - Visitor
- Composite Pattern: (See Project 4)
- Double Dispatch: The mechanism that makes Visitor work, where the operation depends on both the type of the visitor and the type of the element.
Difficulty: Advanced Time estimate: 1-2 weeks Prerequisites: Project 4 (Composite), understanding of recursion.
Real world outcome:
$ python fs_analyzer.py . --visit=size
Analyzing directory '.' with 'size' visitor...
Total size: 4.75 GB
$ python fs_analyzer.py . --visit=find --pattern=*.py
Analyzing directory '.' with 'find' visitor...
Found: ./fs_analyzer.py
Found: ./tests/test_visitors.py
...
Implementation Hints:
- Component Interface:
IFileSystemComponentwith anaccept(IVisitor visitor)method. - Leaf & Composite:
FileandDirectoryclasses implementingIFileSystemComponent.Directorywill have a list of children. - Visitor Interface:
IVisitorwith methods likevisit_file(File f)andvisit_directory(Directory d). - Concrete Visitors:
SizeVisitorwill have atotal_sizeproperty. Itsvisit_filemethod adds the file’s size.FindVisitorwill check the file name against a pattern. - Double Dispatch: The
File.accept(visitor)method will simply callvisitor.visit_file(this). TheDirectory.accept(visitor)method will first callvisitor.visit_directory(this), and then iterate through its children, callingchild.accept(visitor)on each one.
Learning milestones:
- You have a working Composite structure for the filesystem → You’ve reinforced your understanding of Composite.
- You can add a new operation (e.g., a “Permissions Report” visitor) without touching the File or Directory classes → You’ve mastered the Visitor pattern.
- The traversal logic is correctly handled by the
acceptmethods → You understand double dispatch.
Project 8: RPG Character State Machine
- File: LEARN_DESIGN_PATTERNS.md
- Main Programming Language: C#
- Alternative Programming Languages: Java, C++, Python
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 2: Intermediate
- Knowledge Area: Behavioral Patterns
- Software or Tool: Text-based RPG game.
- Main Book: “Head First Design Patterns”
What you’ll build: A simple text-based role-playing game where a character’s behavior (how they attack, defend, or use items) changes based on their current status (e.g., Normal, Poisoned, Enraged, Hidden).
Why it teaches design patterns: This is a classic application of the State and Template Method patterns. The State pattern allows an object (the character) to alter its behavior when its internal state changes. The Template Method defines the skeleton of an action (like TakeTurn), allowing the specific states to override certain parts of it (like ChooseAction).
Core challenges you’ll face:
- Changing behavior based on internal state → maps to delegating behavior from the
Characterclass to a currentIStateobject - Avoiding massive if/else or switch statements → maps to the core problem solved by the State pattern
- Defining a skeleton of an algorithm → maps to a base
CharacterActionclass with aperform()template method - Letting subclasses redefine certain steps → maps to states overriding methods like
get_available_actions()
Key Concepts:
- State Pattern: “Head First Design Patterns” Chapter 10
- Template Method Pattern: “Head First Design Patterns” Chapter 8
- Finite State Machines: The underlying computer science concept.
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Project 2, comfortable with object-oriented concepts.
Real world outcome: A running console game:
Player (Health: 100/100, State: Normal) encounters a Goblin!
Your turn. Actions: [Attack, Defend]
> Attack
Player attacks Goblin for 12 damage!
Goblin attacks Player for 8 damage. Player is POISONED!
Player (Health: 92/100, State: Poisoned)
Your turn. Actions: [Struggle, UseAntidote]
> Struggle
Player struggles, dealing 4 damage! Player takes 5 poison damage.
Implementation Hints:
- Context Class: The
PlayerCharacterclass. It holds a reference to its currentIStateobject. Methods likeAttack()orDefend()will delegate to the current state object:_currentState.Attack(this, target). It also needs aSetState()method. - State Interface:
ICharacterStatewith methods for all possible actions (Attack,Defend,UseItem, etc.). - Concrete States:
NormalState,PoisonedState,EnragedState. Each implements theICharacterStateinterface differently. For example,PoisonedState.Attack()might do less damage thanNormalState.Attack(). - Template Method: You could have an abstract
Turnclass with a template methodexecute(). The steps would bebeforeTurnEffects(),chooseAction(),afterTurnEffects(). ThePoisonedStatecould add poison damage in theafterTurnEffects()step.
Learning milestones:
- The character’s available actions and their outcomes change based on its state object → You’ve mastered the State pattern.
- You can easily add new states (e.g.,
ConfusedState) without modifying thePlayerCharacterclass → You understand the power of state-based delegation. - Common algorithms are defined in a base class while specifics are handled by subclasses/states → You’ve grasped the Template Method pattern.
Project 9: Cross-Platform UI Toolkit
- File: LEARN_DESIGN_PATTERNS.md
- Main Programming Language: C++
- Alternative Programming Languages: Java, C#, Rust
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 4. The “Open Core” Infrastructure
- Difficulty: Level 4: Expert
- Knowledge Area: Structural Patterns / Creational Patterns
- Software or Tool: Console application simulating UI rendering.
- Main Book: “Design Patterns: Elements of Reusable Object-Oriented Software” by GoF
What you’ll build: A system that can create and render a user interface (buttons, windows, menus) for multiple “operating systems” (e.g., Windows, macOS, Linux), where the abstract UI elements are decoupled from their platform-specific implementations.
Why it teaches design patterns: A powerful demonstration of the Abstract Factory and Bridge patterns working together. Abstract Factory provides an interface for creating families of related UI elements (WindowsFactory, MacFactory). Bridge decouples the high-level UI element abstractions (Window, Button) from their low-level platform implementations (WindowsWindowImpl, MacWindowImpl).
Core challenges you’ll face:
- Creating families of related objects → maps to the
GUIFactoryinterface creating both buttons and windows - Decoupling abstraction from implementation → maps to the
Windowclass holding a reference to aWindowImplinterface, not a concrete class - Allowing abstraction and implementation to vary independently → maps to you can create a
RefinedWindowwithout touching implementation, or aGTKWindowImplwithout touching the abstraction - Enforcing that a UI uses elements from only one theme → maps to the Abstract Factory ensuring a
WindowsFactoryonly creates Windows-compatible elements
Key Concepts:
- Abstract Factory Pattern: “Head First Design Patterns” Chapter 4
- Bridge Pattern: Refactoring.Guru - Bridge
- Decoupling: The primary goal of both patterns.
Difficulty: Expert Time estimate: 2-3 weeks
- Prerequisites: Projects 2 & 5. Strong grasp of abstraction, interfaces, and pointers/references.
Real world outcome:
// main.cpp
int main() {
// Environment variable or config determines the factory
GUIFactory* factory = new MacFactory();
Application app(factory);
app.run(); // Creates and renders a window with a button
// ... clean up ...
return 0;
}
// Console Output:
Creating UI with MacFactory...
Drawing a native macOS window border.
Drawing a native Aqua-style button.
Implementation Hints:
- Bridge (Implementation): Create an
IWindowImplinterface with methods likedraw_border(). Create concrete classesWindowsWindowImpl,MacWindowImpl. - Bridge (Abstraction): Create an abstract
Windowclass that holds a pointer toIWindowImpl. Itsdraw()method calls_impl->draw_border(). You can have refined abstractions likeDialogWindow. - Abstract Factory: Create an
IGUIFactoryinterface with methodscreate_window()andcreate_button(). - Concrete Factories:
WindowsFactorywill createWindowobjects equipped with aWindowsWindowImpl, andButtonobjects with aWindowsButtonImpl.MacFactorydoes the same for macOS. - Your client code (the
Application) is configured with a factory. It asks the factory to create UI elements, blissfully unaware of the concrete OS-specific classes being used.
Learning milestones:
- You can switch the entire look-and-feel of the application by changing only the factory → You’ve mastered Abstract Factory.
- The high-level
Windowlogic is completely separate from the low-level drawing code → You’ve mastered the Bridge pattern. - You can add a new UI element (
TextBox) or a new OS (Linux) by adding new classes without a cascade of changes → You understand true decoupling.
Project 10: Command-Line Chat Room
- File: LEARN_DESIGN_PATTERNS.md
- Main Programming Language: Go
- Alternative Programming Languages: Java (with threads), Python (with asyncio), C#
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 3: Advanced
- Knowledge Area: Behavioral Patterns
- Software or Tool: Concurrent console application.
- Main Book: “Design Patterns: Elements of Reusable Object-Oriented Software” by GoF
What you’ll build: A multi-user chat application where User objects communicate with each other, but not directly. All communication is routed through a central ChatRoom object. You can also add a logging layer that intercepts messages.
Why it teaches design patterns: This project is a great way to learn the Mediator and Proxy patterns. Mediator centralizes complex communications between multiple objects, preventing a “spaghetti” of connections. Proxy provides a surrogate or placeholder for another object to control access to it, perfect for logging, caching, or security checks.
Core challenges you’ll face:
- Reducing coupling between many objects → maps to users only knowing about the mediator, not each other
- Centralizing communication logic → maps to the
ChatRoom(Mediator) containing the logic to route messages - Controlling access to an object → maps to a
LoggingProxythat intercepts messages before they reach the real chat room - Adding behavior without changing the object’s code → maps to the proxy adding logging without modifying the
ChatRoomclass
Key Concepts:
- Mediator Pattern: Refactoring.Guru - Mediator
- Proxy Pattern: “Head First Design Patterns” Chapter 11 (Remote Proxy) & Refactoring.Guru (Protection/Logging Proxy)
- Concurrency: Handling multiple users requires basic concurrency concepts (goroutines in Go, threads in Java).
Difficulty: Advanced Time estimate: 1-2 weeks Prerequisites: Understanding of interfaces and basic concurrency.
Real world outcome: A running terminal application where multiple users can chat.
# Terminal 1
> ./chat --user=Alice
Alice has joined.
> Hello everyone!
# Terminal 2
> ./chat --user=Bob
Alice is in the room.
Bob has joined.
[Alice]: Hello everyone!
> Hi Alice!
# Terminal 1
[Bob]: Hi Alice!
# Log file (from the proxy)
[2025-12-20 10:30:01] User Alice sending message: 'Hello everyone!'
[2025-12-20 10:30:15] User Bob sending message: 'Hi Alice!'
Implementation Hints:
- Colleague: Create a
Userclass. It should hold a reference to theIChatRoomMediator. Itssend()method will call the mediator’sbroadcast()method. It also needs areceive()method that the mediator can call. - Mediator: Create an
IChatRoomMediatorinterface withbroadcast(User, message)andregister(User)methods. - Concrete Mediator: The
ChatRoomclass implements the mediator interface. It maintains a list of registeredUserobjects. Itsbroadcastmethod loops through the users and calls theirreceivemethod. - Proxy: Create a
ChatRoomProxythat also implementsIChatRoomMediator. It holds a reference to the realChatRoomobject. Itsbroadcastmethod will first write to a log file and then call the real chat room’sbroadcastmethod. Your application will interact with the proxy, not the real chat room.
Learning milestones:
- Users can communicate without having direct references to each other → You’ve mastered the Mediator pattern.
- Message traffic is logged without the
ChatRoomorUserclasses being aware of it → You’ve mastered the Proxy pattern. - The system can handle multiple concurrent users → You can apply design patterns in a concurrent environment.
Project 11: Document Processing Pipeline
- File: LEARN_DESIGN_PATTERNS.md
- Main Programming Language: Python
- Alternative Programming Languages: Java, C#, Go
- Coolness Level: Level 2: Practical but Forgettable
- Business Potential: 3. The “Service & Support” Model
- Difficulty: Level 2: Intermediate
- Knowledge Area: Behavioral Patterns
- Software or Tool: Console application for processing text files.
- Main Book: “Design Patterns: Elements of Reusable Object-Oriented Software” by GoF
What you’ll build: A system that processes a document through a series of sequential handlers. For example, a request might first be checked by an AuthenticationHandler, then a ValidationHandler, and finally a ContentEnrichmentHandler.
Why it teaches design patterns: This is the canonical example of the Chain of Responsibility pattern. It allows you to create a chain of processing objects (handlers) and pass a request along the chain. Each handler decides whether to process the request or pass it to the next handler in the chain.
Core challenges you’ll face:
- Decoupling sender and receiver → maps to the client only knows about the first handler in the chain
- Allowing multiple objects to handle a request → maps to each handler having a chance to act
- Dynamically configuring the chain → maps to linking handlers together at runtime
- Avoiding a giant
if-elif-elseblock → maps to the core problem solved by this pattern
Key Concepts:
- Chain of Responsibility Pattern: Refactoring.Guru - Chain of Responsibility
- Loose Coupling: The main benefit of the pattern.
Difficulty: Intermediate Time estimate: Weekend Prerequisites: Understanding of linked lists (conceptually) and object composition.
Real world outcome:
$ python pipeline.py document.txt --user=admin
Handler: AuthenticationHandler -> Checking credentials for 'admin'. OK.
Handler: ValidationHandler -> Validating document format. OK.
Handler: ContentEnrichmentHandler -> Adding metadata to document.
Processing complete.
$ python pipeline.py document.txt --user=guest
Handler: AuthenticationHandler -> Checking credentials for 'guest'. FAILED.
Processing stopped: Unauthorized.
Implementation Hints:
- Request Object: Create a
DocumentRequestclass that holds the document content, user credentials, etc. This object will be passed down the chain. - Handler Interface: Create an abstract
Handlerclass or interface. It should have a field for the_next_handlerand two main methods:set_next(handler)to build the chain, andhandle(request). - Handler Implementation: The
handlemethod in the abstract base class should contain the core logic: if there’s a next handler, callself._next_handler.handle(request). - Concrete Handlers: Create classes like
AuthenticationHandlerandValidationHandlerthat inherit fromHandler. They override thehandlemethod. Inside, they perform their specific logic. If they can handle the request and processing should continue, they callsuper().handle(request)to pass it on. If processing should stop, they simply return. - Client: The client code builds the chain:
h1 = AuthHandler(),h2 = ValidHandler(),h1.set_next(h2). Then it kicks off the process by callingh1.handle(request).
Learning milestones:
- Requests flow through a chain of handlers → You understand the basic structure of the pattern.
- Any handler can stop the chain → You understand conditional request passing.
- You can reorder or add new handlers without changing client code → You’ve mastered the flexibility of the pattern.
Project 12: High-Performance Particle System
- File: LEARN_DESIGN_PATTERNS.md
- Main Programming Language: C++
- Alternative Programming Languages: Rust, Java with a graphics library
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 4: Expert
- Knowledge Area: Structural Patterns / Performance Optimization
- Software or Tool: A simple graphics library (like SFML or SDL) or ASCII art rendering.
- Main Book: “Game Programming Patterns” by Robert Nystrom
What you’ll build: A simulation that renders tens of thousands of particles (e.g., rain, snow, sparks) on the screen at a high frame rate by minimizing memory usage.
Why it teaches design patterns: This is the classic use case for the Flyweight pattern. The goal of Flyweight is to fit more objects into available RAM by sharing common state between multiple objects instead of storing it in each object. It’s essential for performance-critical applications.
Core challenges you’ll face:
- Minimizing memory consumption → maps to the core goal of the Flyweight pattern
- Separating intrinsic from extrinsic state → maps to identifying what can be shared vs. what must be unique
- Managing a pool of shared objects → maps to creating a factory that returns existing flyweights or creates new ones
- Passing extrinsic state to methods → maps to the client providing position/velocity to the flyweight’s
drawmethod
Key Concepts:
- Flyweight Pattern: “Game Programming Patterns” by Robert Nystrom - Chapter on Flyweight
- Intrinsic vs. Extrinsic State: Intrinsic is context-independent and shareable (e.g., a particle’s color, texture). Extrinsic is context-dependent and unique (e.g., a particle’s position, velocity).
- Object Pools: A common way to manage flyweight objects.
Difficulty: Expert Time estimate: 2-3 weeks Prerequisites: Project 9, strong grasp of memory management and performance concepts.
Real world outcome: A smooth animation of thousands of particles, where a naive implementation would be slow and choppy.
// Memory usage without Flyweight:
// 10,000 particles * (position(8) + velocity(8) + color(4) + sprite(1024)) bytes = ~10 MB
// Memory usage with Flyweight:
// 1 ParticleType (color+sprite) = ~1 KB
// 10,000 particles * (position(8) + velocity(8) + pointer(8)) bytes = ~240 KB
// Total: ~241 KB -- a massive saving!
Implementation Hints:
- Flyweight Class: Create a
ParticleTypeclass. This will store the intrinsic state: color, sprite/texture, base size. - Flyweight Factory: Create a
ParticleFactoryclass. It will have a methodget_particle_type(color, sprite). This factory maintains a map or hash table of already-createdParticleTypeobjects. When asked for a type, it first checks the map. If it exists, it returns the existing object; otherwise, it creates a new one, adds it to the map, and returns it. - Context Class: The
Particleclass. This stores the extrinsic state: current position, velocity, and a reference/pointer to itsParticleTypeflyweight object. - Client: The main game loop will hold a list of thousands of
Particleobjects. In the update loop, it updates each particle’s position. In the draw loop, it callsparticle.draw(canvas). TheParticle::drawmethod would look like this:_type->draw(canvas, _position). Notice how the extrinsic state (_position) is passed to the flyweight object.
Learning milestones:
- Shared (intrinsic) state is stored in separate objects from unique (extrinsic) state → You understand the core concept of Flyweight.
- A factory manages a pool of shared flyweight objects, preventing duplication → You know how to implement the pattern efficiently.
- Your application can handle a huge number of objects with low memory overhead → You’ve successfully applied the pattern to solve a real performance problem.
Summary
| Project | Main Language | Patterns Learned | Difficulty |
|---|---|---|---|
| 1. Config Manager | Java | Singleton | Beginner |
| 2. Document Converter | Java | Factory Method, Strategy | Intermediate |
| 3. Pizza API | C# | Builder, Decorator | Intermediate |
| 4. Mini GUI Framework | Python | Composite, Observer | Advanced |
| 5. Smart Home Hub | Python | Adapter, Facade | Intermediate |
| 6. Text Editor w/ Undo | Java | Command, Memento | Advanced |
| 7. File System Analyzer | Python | Composite, Visitor, Iterator | Advanced |
| 8. RPG State Machine | C# | State, Template Method | Intermediate |
| 9. Cross-Platform UI | C++ | Abstract Factory, Bridge | Expert |
| 10. Chat Room | Go | Mediator, Proxy | Advanced |
| 11. Processing Pipeline | Python | Chain of Responsibility | Intermediate |
| 12. Particle System | C++ | Flyweight | Expert |