Module 29: Design Patterns in C++
Introduction
As software grows in size and complexity, writing maintainable, reusable, and scalable code becomes increasingly important. Design Patterns are proven solutions to common software design problems.
A design pattern is not a finished program, but a reusable template that helps solve recurring design challenges.
Design patterns improve:
- Code reusability
- Scalability
- Maintainability
- Flexibility
- Readability
- Loose coupling
They are widely used in:
- Game Engines
- Operating Systems
- Web Frameworks
- GUI Applications
- Database Systems
- AI Software
- Enterprise Applications
Learning Objectives
After completing this module, you will understand:
- What are Design Patterns?
- Singleton Pattern
- Factory Pattern
- Observer Pattern
- Strategy Pattern
- Builder Pattern
- Adapter Pattern
- Decorator Pattern
- Real-world use cases
- Best practices
What are Design Patterns?
A design pattern is a reusable solution to a common software design problem.
Instead of reinventing solutions, developers use proven patterns.
Problem
↓
Choose Pattern
↓
Reusable Solution
↓
Maintainable Code
Categories of Design Patterns
| Category | Purpose |
|---|---|
| Creational | Object creation |
| Structural | Object relationships |
| Behavioral | Communication between objects |
Our module covers all three categories.
1. Singleton Pattern
What is Singleton?
Singleton ensures that only one object of a class exists throughout the application.
Examples:
- Logger
- Configuration Manager
- Database Connection
- Cache Manager
Singleton Diagram
Application
│
▼
Singleton Object
│
Only One Instance
Singleton Example
1#include <iostream> 2 3class Singleton 4{ 5private: 6 7 Singleton() {} 8 9public: 10 11 Singleton(const Singleton&) = delete; 12 Singleton& operator=(const Singleton&) = delete; 13 14 static Singleton& getInstance() 15 { 16 static Singleton instance; 17 return instance; 18 } 19 20 void show() 21 { 22 std::cout << "Singleton Instance\n"; 23 } 24}; 25 26int main() 27{ 28 Singleton::getInstance().show(); 29}
Output
Singleton Instance
When to Use Singleton
- Logger
- Configuration
- Printer Manager
- Database Connection Pool
- Global Settings
2. Factory Pattern
What is Factory?
Instead of creating objects directly with new, a factory creates the correct object.
Factory Diagram
Client
│
Factory
├── Circle
├── Square
└── Rectangle
Factory Example
1#include <iostream> 2#include <memory> 3 4class Shape 5{ 6public: 7 virtual void draw() = 0; 8 virtual ~Shape() = default; 9}; 10 11class Circle : public Shape 12{ 13public: 14 void draw() override 15 { 16 std::cout << "Drawing Circle\n"; 17 } 18}; 19 20class Square : public Shape 21{ 22public: 23 void draw() override 24 { 25 std::cout << "Drawing Square\n"; 26 } 27}; 28 29class ShapeFactory 30{ 31public: 32 static std::unique_ptr<Shape> create(int type) 33 { 34 if(type == 1) 35 return std::make_unique<Circle>(); 36 37 return std::make_unique<Square>(); 38 } 39}; 40 41int main() 42{ 43 auto shape = ShapeFactory::create(1); 44 45 shape->draw(); 46}
Output
Drawing Circle
Factory Benefits
- Loose coupling
- Easy to extend
- Hides object creation
3. Observer Pattern
What is Observer?
One object notifies multiple dependent objects whenever its state changes.
Examples:
- YouTube Subscribers
- Weather Apps
- Stock Market
- Notifications
Observer Diagram
Subject
┌────┼────┐
Observer Observer Observer
Observer Example
1#include <iostream> 2#include <vector> 3 4class Observer 5{ 6public: 7 virtual void update() = 0; 8 virtual ~Observer() = default; 9}; 10 11class Subscriber : public Observer 12{ 13public: 14 void update() override 15 { 16 std::cout << "New Notification\n"; 17 } 18}; 19 20class Channel 21{ 22 std::vector<Observer*> observers; 23 24public: 25 26 void subscribe(Observer* o) 27 { 28 observers.push_back(o); 29 } 30 31 void notify() 32 { 33 for(auto observer : observers) 34 observer->update(); 35 } 36}; 37 38int main() 39{ 40 Subscriber s1, s2; 41 42 Channel channel; 43 44 channel.subscribe(&s1); 45 channel.subscribe(&s2); 46 47 channel.notify(); 48}
Output
New Notification
New Notification
Observer Benefits
- Loose coupling
- Event-driven programming
- Easy notifications
4. Strategy Pattern
What is Strategy?
Allows selecting an algorithm at runtime.
Example:
Payment methods:
- Credit Card
- UPI
- PayPal
Strategy Diagram
Client
│
Strategy
├── Card
├── UPI
└── PayPal
Strategy Example
1#include <iostream> 2#include <memory> 3 4class Payment 5{ 6public: 7 virtual void pay() = 0; 8 virtual ~Payment() = default; 9}; 10 11class Card : public Payment 12{ 13public: 14 void pay() override 15 { 16 std::cout << "Paid by Card\n"; 17 } 18}; 19 20class UPI : public Payment 21{ 22public: 23 void pay() override 24 { 25 std::cout << "Paid by UPI\n"; 26 } 27}; 28 29int main() 30{ 31 std::unique_ptr<Payment> payment = std::make_unique<UPI>(); 32 33 payment->pay(); 34}
Output
Paid by UPI
Strategy Benefits
- Easy to switch algorithms
- Open/Closed Principle
- Removes large if-else chains
5. Builder Pattern
What is Builder?
Builder constructs complex objects step by step.
Example:
Building a computer.
Builder Diagram
Builder
↓
CPU
↓
RAM
↓
Storage
↓
Computer
Builder Example
1#include <iostream> 2#include <string> 3 4class Computer 5{ 6 std::string cpu; 7 int ram{}; 8 int storage{}; 9 10public: 11 12 Computer& setCPU(const std::string& value) 13 { 14 cpu = value; 15 return *this; 16 } 17 18 Computer& setRAM(int value) 19 { 20 ram = value; 21 return *this; 22 } 23 24 Computer& setStorage(int value) 25 { 26 storage = value; 27 return *this; 28 } 29 30 void show() const 31 { 32 std::cout << cpu << " " 33 << ram << "GB " 34 << storage << "GB\n"; 35 } 36}; 37 38int main() 39{ 40 Computer pc; 41 42 pc.setCPU("Intel i7") 43 .setRAM(16) 44 .setStorage(512); 45 46 pc.show(); 47}
Output
Intel i7 16GB 512GB
Builder Benefits
- Readable object creation
- Handles many optional parameters
- Easier maintenance
6. Adapter Pattern
What is Adapter?
Converts one interface into another expected by the client.
Examples:
- USB-C to HDMI
- Power Adapter
- Legacy API integration
Adapter Diagram
Client
↓
Adapter
↓
Legacy Class
Adapter Example
1#include <iostream> 2 3class LegacyPrinter 4{ 5public: 6 void oldPrint() 7 { 8 std::cout << "Printing using Legacy Printer\n"; 9 } 10}; 11 12class PrinterAdapter 13{ 14 LegacyPrinter printer; 15 16public: 17 18 void print() 19 { 20 printer.oldPrint(); 21 } 22}; 23 24int main() 25{ 26 PrinterAdapter adapter; 27 28 adapter.print(); 29}
Output
Printing using Legacy Printer
Adapter Benefits
- Reuses old code
- Integrates third-party libraries
- No changes to legacy classes
7. Decorator Pattern
What is Decorator?
Adds new functionality to an object without modifying its class.
Example:
Coffee
Coffee
↓
Milk
↓
Sugar
↓
Chocolate
Decorator Example
1#include <iostream> 2#include <memory> 3 4class Coffee 5{ 6public: 7 virtual std::string description() = 0; 8 virtual ~Coffee() = default; 9}; 10 11class BasicCoffee : public Coffee 12{ 13public: 14 std::string description() override 15 { 16 return "Coffee"; 17 } 18}; 19 20class MilkDecorator : public Coffee 21{ 22 std::unique_ptr<Coffee> coffee; 23 24public: 25 26 MilkDecorator(std::unique_ptr<Coffee> c) 27 : coffee(std::move(c)) 28 { 29 } 30 31 std::string description() override 32 { 33 return coffee->description() + " + Milk"; 34 } 35}; 36 37int main() 38{ 39 std::unique_ptr<Coffee> coffee = 40 std::make_unique<MilkDecorator>( 41 std::make_unique<BasicCoffee>()); 42 43 std::cout << coffee->description(); 44}
Output
Coffee + Milk
Decorator Benefits
- Extend functionality dynamically
- Avoid subclass explosion
- Open/Closed Principle
Pattern Comparison
| Pattern | Category | Purpose | Real Example |
|---|---|---|---|
| Singleton | Creational | One instance | Logger |
| Factory | Creational | Object creation | GUI widgets |
| Observer | Behavioral | Event notification | YouTube subscriptions |
| Strategy | Behavioral | Change algorithms | Payment methods |
| Builder | Creational | Complex object creation | Computer configuration |
| Adapter | Structural | Interface conversion | USB adapter |
| Decorator | Structural | Add functionality | Coffee toppings |
Real-World Applications
| Pattern | Used In |
|---|---|
| Singleton | Database connections, logging |
| Factory | Game engines, GUI frameworks |
| Observer | Chat apps, stock markets |
| Strategy | AI algorithms, payment gateways |
| Builder | REST API requests, object configuration |
| Adapter | Legacy systems, external APIs |
| Decorator | UI components, middleware |
Best Practices
✅ Prefer composition over inheritance.
✅ Follow the SOLID principles.
✅ Keep classes focused on a single responsibility.
✅ Use smart pointers for memory management.
✅ Choose the simplest pattern that solves the problem.
✅ Avoid using patterns unnecessarily.
Common Mistakes
Overusing Singleton
Avoid making everything global.
Large Factory Classes
Split factories when they become too complex.
Memory Leaks in Observer
Unsubscribe observers or use smart pointers for lifetime management.
Too Many Decorators
Excessive layering can make debugging difficult.
Wrong Pattern Selection
Choose the pattern based on the problem, not because it is popular.
Interview Questions
1. What are Design Patterns?
Reusable solutions to common software design problems.
2. What are the three categories of Design Patterns?
- Creational
- Structural
- Behavioral
3. When should Singleton be used?
When exactly one shared instance is required, such as a logger or configuration manager.
4. Why use the Factory Pattern?
To centralise object creation and reduce coupling between clients and concrete classes.
5. What problem does the Observer Pattern solve?
It enables automatic notification of dependent objects when a subject changes state.
6. Why is the Strategy Pattern useful?
It allows algorithms to be selected or changed at runtime without modifying client code.
7. When should the Builder Pattern be used?
When constructing complex objects with many optional parameters or configuration steps.
8. What is the purpose of the Adapter Pattern?
To make incompatible interfaces work together.
9. What is the Decorator Pattern?
A structural pattern that adds responsibilities to objects dynamically without changing their original implementation.
10. Which design pattern is most commonly used for logging?
Singleton Pattern.
Module Summary
In this module, you learned:
- What design patterns are and why they matter
- The three categories of design patterns
- Implementing the Singleton pattern for single-instance classes
- Creating objects using the Factory pattern
- Building event-driven systems with the Observer pattern
- Switching algorithms dynamically with the Strategy pattern
- Constructing complex objects using the Builder pattern
- Integrating incompatible interfaces with the Adapter pattern
- Extending object behaviour using the Decorator pattern
- Best practices for writing clean, scalable, and maintainable object-oriented C++ applications using design patterns