Design patterns are typical solutions to common problems in software design. They are like blueprints that can be customized to solve a particular design problem in your code. Understanding design patterns is crucial for writing efficient, maintainable, and scalable code.
Key Concepts
-
What are Design Patterns?
- Reusable solutions to common software design problems.
- Not code, but templates for how to solve a problem in various situations.
- Help in improving code readability and reusability.
-
Types of Design Patterns:
- Creational Patterns: Deal with object creation mechanisms.
- Structural Patterns: Deal with object composition or structure.
- Behavioral Patterns: Deal with object interaction and responsibility.
Creational Patterns
Singleton Pattern
Ensures a class has only one instance and provides a global point of access to it.
Example:
class Singleton { private: static Singleton* instance; Singleton() {} // Private constructor public: static Singleton* getInstance() { if (instance == nullptr) { instance = new Singleton(); } return instance; } }; // Initialize static member Singleton* Singleton::instance = nullptr; int main() { Singleton* s1 = Singleton::getInstance(); Singleton* s2 = Singleton::getInstance(); if (s1 == s2) { std::cout << "Both instances are the same." << std::endl; } return 0; }
Explanation:
- The constructor is private, so no other class can instantiate it.
getInstance
method ensures only one instance is created.
Factory Pattern
Defines an interface for creating an object but lets subclasses alter the type of objects that will be created.
Example:
class Product { public: virtual void use() = 0; }; class ConcreteProductA : public Product { public: void use() override { std::cout << "Using Product A" << std::endl; } }; class ConcreteProductB : public Product { public: void use() override { std::cout << "Using Product B" << std::endl; } }; class Factory { public: static Product* createProduct(char type) { if (type == 'A') { return new ConcreteProductA(); } else if (type == 'B') { return new ConcreteProductB(); } return nullptr; } }; int main() { Product* product = Factory::createProduct('A'); product->use(); delete product; product = Factory::createProduct('B'); product->use(); delete product; return 0; }
Explanation:
Factory
class creates objects without exposing the creation logic to the client.- The client uses the factory to get the object of the desired type.
Structural Patterns
Adapter Pattern
Allows incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces.
Example:
class Target { public: virtual void request() = 0; }; class Adaptee { public: void specificRequest() { std::cout << "Specific request" << std::endl; } }; class Adapter : public Target { private: Adaptee* adaptee; public: Adapter(Adaptee* a) : adaptee(a) {} void request() override { adaptee->specificRequest(); } }; int main() { Adaptee* adaptee = new Adaptee(); Target* target = new Adapter(adaptee); target->request(); delete adaptee; delete target; return 0; }
Explanation:
Adapter
class makesAdaptee
's interface compatible withTarget
's interface.
Behavioral Patterns
Observer Pattern
Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
Example:
#include <iostream> #include <vector> class Observer { public: virtual void update() = 0; }; class Subject { private: std::vector<Observer*> observers; public: void attach(Observer* observer) { observers.push_back(observer); } void notify() { for (Observer* observer : observers) { observer->update(); } } }; class ConcreteObserver : public Observer { public: void update() override { std::cout << "Observer updated" << std::endl; } }; int main() { Subject subject; ConcreteObserver observer1, observer2; subject.attach(&observer1); subject.attach(&observer2); subject.notify(); return 0; }
Explanation:
Subject
maintains a list of observers and notifies them of any state changes.ConcreteObserver
implements theObserver
interface and updates itself when notified.
Practical Exercises
Exercise 1: Implement Singleton Pattern
Task: Implement a Singleton class for a Logger that writes messages to a file.
Solution:
#include <iostream> #include <fstream> #include <string> class Logger { private: static Logger* instance; std::ofstream logFile; Logger() { logFile.open("log.txt", std::ios::app); } public: static Logger* getInstance() { if (instance == nullptr) { instance = new Logger(); } return instance; } void log(const std::string& message) { logFile << message << std::endl; } ~Logger() { logFile.close(); } }; Logger* Logger::instance = nullptr; int main() { Logger* logger = Logger::getInstance(); logger->log("This is a log message."); return 0; }
Exercise 2: Implement Factory Pattern
Task: Create a factory that produces different types of shapes (Circle, Square) and a method to draw them.
Solution:
#include <iostream> class Shape { public: virtual void draw() = 0; }; class Circle : public Shape { public: void draw() override { std::cout << "Drawing Circle" << std::endl; } }; class Square : public Shape { public: void draw() override { std::cout << "Drawing Square" << std::endl; } }; class ShapeFactory { public: static Shape* createShape(const std::string& type) { if (type == "Circle") { return new Circle(); } else if (type == "Square") { return new Square(); } return nullptr; } }; int main() { Shape* shape1 = ShapeFactory::createShape("Circle"); shape1->draw(); delete shape1; Shape* shape2 = ShapeFactory::createShape("Square"); shape2->draw(); delete shape2; return 0; }
Conclusion
Design patterns are essential tools in a programmer's toolkit. They provide tested, proven development paradigms, making your code more flexible, reusable, and easier to maintain. By understanding and applying these patterns, you can solve common design problems more efficiently and effectively. In the next module, we will delve into code optimization techniques to further enhance your programming skills.
C++ Programming Course
Module 1: Introduction to C++
- Introduction to C++
- Setting Up the Development Environment
- Basic Syntax and Structure
- Variables and Data Types
- Input and Output
Module 2: Control Structures
Module 3: Functions
Module 4: Arrays and Strings
Module 5: Pointers and References
- Introduction to Pointers
- Pointer Arithmetic
- Pointers and Arrays
- References
- Dynamic Memory Allocation
Module 6: Object-Oriented Programming
- Introduction to OOP
- Classes and Objects
- Constructors and Destructors
- Inheritance
- Polymorphism
- Encapsulation and Abstraction
Module 7: Advanced Topics
- Templates
- Exception Handling
- File I/O
- Standard Template Library (STL)
- Lambda Expressions
- Multithreading