Design Patterns in C++
Master essential software design patterns for building maintainable C++ applications
Learn proven design patterns that solve recurring problems and provide a shared vocabulary for developers.
A Simple Example
#include <iostream>
#include <memory>
#include <vector>
// Strategy Pattern - interchangeable algorithms
class SortStrategy {
public:
virtual ~SortStrategy() = default;
virtual void sort(std::vector<int>& data) const = 0;
};
class QuickSort : public SortStrategy {
public:
void sort(std::vector<int>& data) const override {
std::cout << "Sorting with QuickSort\n";
// Implementation would go here
}
};
class MergeSort : public SortStrategy {
public:
void sort(std::vector<int>& data) const override {
std::cout << "Sorting with MergeSort\n";
// Implementation would go here
}
};
// Factory Pattern - object creation
class Logger {
public:
virtual ~Logger() = default;
virtual void log(const std::string& msg) = 0;
};
class FileLogger : public Logger {
public:
void log(const std::string& msg) override {
std::cout << "[FILE] " << msg << "\n";
}
};
class ConsoleLogger : public Logger {
public:
void log(const std::string& msg) override {
std::cout << "[CONSOLE] " << msg << "\n";
}
};
class LoggerFactory {
public:
static std::unique_ptr<Logger> create(const std::string& type) {
if (type == "file") {
return std::make_unique<FileLogger>();
} else {
return std::make_unique<ConsoleLogger>();
}
}
};
int main() {
// Strategy Pattern usage
std::vector<int> data{5, 2, 8, 1, 9};
std::unique_ptr<SortStrategy> strategy = std::make_unique<QuickSort>();
strategy->sort(data);
// Factory Pattern usage
auto logger = LoggerFactory::create("console");
logger->log("Application started");
return 0;
}
Breaking It Down
Strategy Pattern
- Problem: Need multiple algorithms that are interchangeable at runtime
- Solution: Define family of algorithms, encapsulate each one, make them interchangeable
- Example: Different sorting algorithms, payment methods, compression strategies
- Remember: Lets the algorithm vary independently from clients that use it
Factory Pattern
- Problem: Object creation logic is complex or needs to be centralized
- Solution: Define an interface for creating objects, let subclasses decide which class to instantiate
- Example: Logger factory, document parsers, database connections
- Remember: Decouples object creation from usage, enabling flexibility
Singleton Pattern
- Problem: Need exactly one instance of a class (configuration, cache, thread pool)
- Solution: Make constructor private, provide static getInstance() method
- Caution: Global state makes testing hard, consider alternatives first
- Remember: Thread-safe singleton needs careful implementation in C++
Observer Pattern
- Problem: One object changes state, many others need to be notified
- Solution: Define one-to-many dependency, observers register with subject
- Example: Event systems, MVC frameworks, pub-sub messaging
- Remember: Subject doesn't know concrete observers, only the interface
Why This Matters
- Design patterns are proven solutions to recurring problems accumulated over decades of software engineering.
- They provide a shared vocabulary for developers - saying "use a Factory" conveys a complete design in two words.
- Knowing patterns makes you a better architect - you'll recognize when to apply each pattern and how to structure complex systems.
Critical Insight
Design patterns aren't about memorizing code - they're about recognizing problems. When you see "I need multiple algorithms that are interchangeable," think Strategy. "I need to notify multiple objects when something changes," think Observer. "I need exactly one instance," think Singleton.
Patterns give you a mental toolbox for architecture decisions. They're proven solutions that have been refined over decades. More importantly, they create a shared language - when you tell another developer "we'll use the Factory pattern here," they instantly understand the design without you explaining the details.
The key is knowing WHEN to use each pattern, not just HOW. Don't force patterns where they don't fit - that creates unnecessary complexity. Use them when you recognize the problem they solve.
Best Practices
Learn to recognize problems: Focus on when to use patterns, not just how to implement them.
Don't force patterns: If a simple solution works, use it. Patterns add complexity and should solve a real problem.
Combine patterns: Real systems often use multiple patterns together (Factory + Strategy, Observer + Singleton).
Prefer composition: Modern C++ favors composition and templates over inheritance-heavy patterns.
Use RAII with patterns: Apply RAII principles for automatic resource management in pattern implementations.
Common Mistakes
Overusing patterns: Don't force patterns where they don't fit. Simple problems need simple solutions.
Singleton abuse: Singletons are global state, which makes testing hard. Consider dependency injection instead.
Ignoring modern alternatives: Some patterns are less relevant with modern C++ features (smart pointers, lambdas, etc.).
Pattern cargo cult: Using patterns because they're "proper" without understanding the problem they solve.
Debug Challenge
This Singleton implementation isn't thread-safe. Click the highlighted line to see the fix:
Quick Quiz
- What problem does the Singleton pattern solve?
- Which pattern encapsulates a request as an object?
- What's the key benefit of the Factory pattern?
Practice Playground
Time to try out what you just learned! Play with the example code below, experiment by making changes and running the code to deepen your understanding.
Output:
Error:
Lesson Progress
- Fix This Code
- Quick Quiz
- Practice Playground - run once