Behavioral_patterns
Behavioral Patterns
Section titled “Behavioral Patterns”Behavioral design patterns define how objects communicate and assign responsibilities between each other. They help manage complex control flow and reduce coupling.
1. Observer Pattern
Section titled “1. Observer Pattern”One-to-many dependency where subjects notify observers of state changes.
Use Cases
Section titled “Use Cases”- Event handling systems
- Model-View-Controller (MVC)
- Pub/Sub messaging
- UI updates based on data changes
Implementation
Section titled “Implementation”#include <iostream>#include <vector>#include <memory>#include <string>
// Observer interfaceclass Observer {public: virtual ~Observer() = default; virtual void update(const std::string& message) = 0;};
// Concrete observersclass ConcreteObserverA : public Observer { std::string name_;public: explicit ConcreteObserverA(const std::string& name) : name_(name) {}
void update(const std::string& message) override { std::cout << "Observer " << name_ << " received: " << message << "\n"; }};
// Subject (Observable)class Subject { std::vector<Observer*> observers_; std::string state_;
public: void attach(Observer* observer) { observers_.push_back(observer); }
void detach(Observer* observer) { auto it = std::find(observers_.begin(), observers_.end(), observer); if (it != observers_.end()) { observers_.erase(it); } }
void notify() { for (auto* obs : observers_) { obs->update(state_); } }
void setState(const std::string& state) { state_ = state; notify(); }
std::string getState() const { return state_; }};
// Usageint main() { Subject subject; ConcreteObserverA obs1("A"); ConcreteObserverA obs2("B");
subject.attach(&obs1); subject.attach(&obs2);
subject.setState("Hello"); // Both observers notified
subject.detach(&obs1); subject.setState("World"); // Only obs2 notified
return 0;}Modern C++ Implementation (using std::function)
Section titled “Modern C++ Implementation (using std::function)”#include <iostream>#include <vector>#include <functional>
class Event { std::vector<std::function<void()>> listeners_;
public: void subscribe(std::function<void()> callback) { listeners_.push_back(std::move(callback)); }
void emit() { for (auto& listener : listeners_) { listener(); } }};2. Strategy Pattern
Section titled “2. Strategy Pattern”Defines a family of algorithms, encapsulates each one, and makes them interchangeable.
Use Cases
Section titled “Use Cases”- Sorting algorithms
- Payment processing
- Compression algorithms
- Pathfinding algorithms
Implementation
Section titled “Implementation”#include <iostream>#include <vector>#include <memory>
// Strategy interfaceclass SortStrategy {public: virtual ~SortStrategy() = default; virtual void sort(std::vector<int>& data) = 0;};
// Concrete strategiesclass BubbleSort : public SortStrategy {public: void sort(std::vector<int>& data) override { std::cout << "Sorting using bubble sort\n"; for (size_t i = 0; i < data.size(); i++) { for (size_t j = 0; j < data.size() - i - 1; j++) { if (data[j] > data[j + 1]) { std::swap(data[j], data[j + 1]); } } } }};
class QuickSort : public SortStrategy {public: void sort(std::vector<int>& data) override { std::cout << "Sorting using quick sort\n"; quickSort(data, 0, data.size() - 1); }
private: void quickSort(std::vector<int>& data, int low, int high) { if (low < high) { int pi = partition(data, low, high); quickSort(data, low, pi - 1); quickSort(data, pi + 1, high); } }
int partition(std::vector<int>& data, int low, int high) { int pivot = data[high]; int i = low - 1; for (int j = low; j < high; j++) { if (data[j] <= pivot) { i++; std::swap(data[i], data[j]); } } std::swap(data[i + 1], data[high]); return i + 1; }};
// Contextclass Sorter { std::unique_ptr<SortStrategy> strategy_;
public: void setStrategy(std::unique_ptr<SortStrategy> strategy) { strategy_ = std::move(strategy); }
void sortData(std::vector<int>& data) { if (strategy_) { strategy_->sort(data); } }};
// Usageint main() { std::vector<int> data = {5, 2, 8, 1, 9};
Sorter sorter;
sorter.setStrategy(std::make_unique<BubbleSort>()); sorter.sortData(data);
sorter.setStrategy(std::make_unique<QuickSort>()); sorter.sortData(data);
return 0;}3. Command Pattern
Section titled “3. Command Pattern”Encapsulates a request as an object, allowing parameterization and queuing.
Use Cases
Section titled “Use Cases”- Undo/Redo functionality
- Transaction management
- Task scheduling
- Macro recording
Implementation
Section titled “Implementation”#include <iostream>#include <stack>#include <memory>
// Command interfaceclass Command {public: virtual ~Command() = default; virtual void execute() = 0; virtual void undo() = 0;};
// Receiverclass TextEditor { std::string content_;
public: void addText(const std::string& text) { content_ += text; }
void removeText(size_t count) { if (count <= content_.size()) { content_.erase(content_.size() - count); } }
std::string getContent() const { return content_; }};
// Concrete commandsclass AddTextCommand : public Command { TextEditor& editor_; std::string text_;
public: AddTextCommand(TextEditor& editor, const std::string& text) : editor_(editor), text_(text) {}
void execute() override { editor_.addText(text_); }
void undo() override { editor_.removeText(text_.size()); }};
class RemoveTextCommand : public Command { TextEditor& editor_; size_t count_;
public: RemoveTextCommand(TextEditor& editor, size_t count) : editor_(editor), count_(count) {}
void execute() override { editor_.removeText(count_); }
void undo() override { // Would need to store removed text for proper undo }};
// Invoker with undo/redoclass EditorManager { std::stack<std::unique_ptr<Command>> history_; std::stack<std::unique_ptr<Command>> redoStack_;
public: void executeCommand(std::unique_ptr<Command> cmd) { cmd->execute(); history_.push(std::move(cmd)); redoStack_ = std::stack<std::unique_ptr<Command>>(); // Clear redo }
void undo() { if (!history_.empty()) { auto& cmd = history_.top(); cmd->undo(); redoStack_.push(std::move(history_.top())); history_.pop(); } }
void redo() { if (!redoStack_.empty()) { auto& cmd = redoStack_.top(); cmd->execute(); history_.push(std::move(redoStack_.top())); redoStack_.pop(); } }};4. Template Method Pattern
Section titled “4. Template Method Pattern”Defines the skeleton of an algorithm, deferring some steps to subclasses.
Use Cases
Section titled “Use Cases”- Framework hooks
- Data processing pipelines
- Build processes
Implementation
Section titled “Implementation”#include <iostream>#include <string>#include <vector>
// Abstract base classclass DataProcessor {protected: // Template method void process() { loadData(); validateData(); transformData(); saveResults(); }
// Steps to be implemented by subclasses virtual void loadData() = 0; virtual void saveResults() = 0;
// Default implementation virtual void validateData() { std::cout << "Validating data...\n"; }
// Required step virtual void transformData() = 0;
public: void run() { process(); }};
class CSVProcessor : public DataProcessor {protected: void loadData() override { std::cout << "Loading CSV file...\n"; }
void transformData() override { std::cout << "Processing CSV rows...\n"; }
void saveResults() override { std::cout << "Saving CSV output...\n"; }};
class JSONProcessor : public DataProcessor {protected: void loadData() override { std::cout << "Loading JSON file...\n"; }
void transformData() override { std::cout << "Processing JSON objects...\n"; }
void saveResults() override { std::cout << "Saving JSON output...\n"; }};
// Usageint main() { CSVProcessor csv; csv.run();
std::cout << "\n";
JSONProcessor json; json.run();
return 0;}5. State Pattern
Section titled “5. State Pattern”Allows an object to alter its behavior when its internal state changes.
Use Cases
Section titled “Use Cases”- State machines
- Workflow engines
- Game states
- Order processing
Implementation
Section titled “Implementation”#include <iostream>#include <memory>#include <string>
// Forward declarationclass TCPState;
// Contextclass TCPConnection { std::unique_ptr<TCPState> state_;
public: TCPConnection();
void open(); void close(); void acknowledge();
void changeState(std::unique_ptr<TCPState> state);};
// State interfaceclass TCPState {public: virtual void open(TCPConnection* conn) {} virtual void close(TCPConnection* conn) {} virtual void acknowledge(TCPConnection* conn) {}
virtual std::string getName() const = 0; virtual ~TCPState() = default;};
// Concrete statesclass TCPClosed : public TCPState {public: void open(TCPConnection* conn) override;
std::string getName() const override { return "Closed"; }};
class TCPEstablished : public TCPState {public: void close(TCPConnection* conn) override; void acknowledge(TCPConnection* conn) override;
std::string getName() const override { return "Established"; }};
class TCPListen : public TCPState {public: void acknowledge(TCPConnection* conn) override;
std::string getName() const override { return "Listen"; }};
// ImplementationTCPConnection::TCPConnection() { state_ = std::make_unique<TCPClosed>();}
void TCPConnection::open() { state_->open(this);}
void TCPConnection::close() { state_->close(this);}
void TCPConnection::acknowledge() { state_->acknowledge(this);}
void TCPConnection::changeState(std::unique_ptr<TCPState> state) { state_ = std::move(state);}
void TCPClosed::open(TCPConnection* conn) { std::cout << "Opening connection\n"; conn->changeState(std::make_unique<TCPEstablished>());}
void TCPEstablished::close(TCPConnection* conn) { std::cout << "Closing connection\n"; conn->changeState(std::make_unique<TCPListen>());}
void TCPEstablished::acknowledge(TCPConnection* conn) { std::cout << "Acknowledging\n";}
void TCPListen::acknowledge(TCPConnection* conn) { std::cout << "Listen -> Established\n"; conn->changeState(std::make_unique<TCPEstablished>());}
// Usageint main() { TCPConnection conn;
std::cout << "Initial: " << conn.getState() << "\n"; conn.open(); conn.acknowledge(); conn.close();
return 0;}6. Chain of Responsibility
Section titled “6. Chain of Responsibility”Passes requests along a chain of handlers until one handles it.
Use Cases
Section titled “Use Cases”- Event handling
- Logging with different levels
- Authentication/Authorization
- Caching
Implementation
Section titled “Implementation”#include <iostream>#include <string>#include <memory>
// Requeststruct Request { std::string message; int priority;};
// Handler interfaceclass Handler { std::unique_ptr<Handler> next_;
public: virtual ~Handler() = default;
void setNext(std::unique_ptr<Handler> next) { next_ = std::move(next); }
void handle(const Request& req) { if (canHandle(req)) { process(req); } else if (next_) { next_->handle(req); } else { std::cout << "No handler found\n"; } }
protected: virtual bool canHandle(const Request& req) = 0; virtual void process(const Request& req) = 0;};
// Concrete handlersclass DebugHandler : public Handler {protected: bool canHandle(const Request& req) override { return req.priority <= 1; }
void process(const Request& req) override { std::cout << "Debug: " << req.message << "\n"; }};
class InfoHandler : public Handler {protected: bool canHandle(const Request& req) override { return req.priority <= 2; }
void process(const Request& req) override { std::cout << "Info: " << req.message << "\n"; }};
class ErrorHandler : public Handler {protected: bool canHandle(const Request& req) override { return req.priority <= 3; }
void process(const Request& req) override { std::cout << "Error: " << req.message << "\n"; }};
// Usageint main() { auto error = std::make_unique<ErrorHandler>(); auto info = std::make_unique<InfoHandler>(); auto debug = std::make_unique<DebugHandler>();
debug->setNext(std::move(info)); info->setNext(std::move(error));
Handler& chain = *debug;
chain.handle({"Debug message", 1}); chain.handle({"Info message", 2}); chain.handle({"Error message", 3});
return 0;}7. Visitor Pattern
Section titled “7. Visitor Pattern”Separates algorithm from object structure.
Use Cases
Section titled “Use Cases”- Operating on element collections
- Compiler AST traversal
- Document conversion
- Tax calculation systems
Implementation
Section titled “Implementation”#include <iostream>#include <memory>#include <vector>
// Forward declarationsclass Circle;class Rectangle;class Triangle;
// Visitor interfaceclass ShapeVisitor {public: virtual ~ShapeVisitor() = default; virtual void visit(Circle& circle) = 0; virtual void visit(Rectangle& rectangle) = 0; virtual void visit(Triangle& triangle) = 0;};
// Element interfaceclass Shape {public: virtual void accept(ShapeVisitor& visitor) = 0; virtual ~Shape() = default;};
// Concrete elementsclass Circle : public Shape { double radius_;
public: explicit Circle(double r) : radius_(r) {} double radius() const { return radius_; }
void accept(ShapeVisitor& visitor) override { visitor.visit(*this); }};
class Rectangle : public Shape { double width_, height_;
public: Rectangle(double w, double h) : width_(w), height_(h) {} double width() const { return width_; } double height() const { return height_; }
void accept(ShapeVisitor& visitor) override { visitor.visit(*this); }};
class Triangle : public Shape { double base_, height_;
public: Triangle(double b, double h) : base_(b), height_(h) {} double base() const { return base_; } double height() const { return height_; }
void accept(ShapeVisitor& visitor) override { visitor.visit(*this); }};
// Concrete visitorsclass AreaCalculator : public ShapeVisitor { double totalArea_ = 0;
public: void visit(Circle& circle) override { totalArea_ += 3.14159 * circle.radius() * circle.radius(); }
void visit(Rectangle& rectangle) override { totalArea_ += rectangle.width() * rectangle.height(); }
void visit(Triangle& triangle) override { totalArea_ += 0.5 * triangle.base() * triangle.height(); }
double area() const { return totalArea_; }};
class DrawingVisitor : public ShapeVisitor {public: void visit(Circle& circle) override { std::cout << "Drawing circle with r=" << circle.radius() << "\n"; }
void visit(Rectangle& rectangle) override { std::cout << "Drawing rectangle " << rectangle.width() << "x" << rectangle.height() << "\n"; }
void visit(Triangle& triangle) override { std::cout << "Drawing triangle b=" << triangle.base() << " h=" << triangle.height() << "\n"; }};
// Usageint main() { std::vector<std::unique_ptr<Shape>> shapes; shapes.push_back(std::make_unique<Circle>(5)); shapes.push_back(std::make_unique<Rectangle>(4, 6)); shapes.push_back(std::make_unique<Triangle>(3, 4));
AreaCalculator areaCalc; DrawingVisitor drawer;
for (auto& shape : shapes) { shape->accept(areaCalc); shape->accept(drawer); }
std::cout << "Total area: " << areaCalc.area() << "\n";
return 0;}Summary
Section titled “Summary”| Pattern | Intent | Use Case |
|---|---|---|
| Observer | Notify changes | Event systems |
| Strategy | Swap algorithms | Sorting, payment |
| Command | Encapsulate requests | Undo/redo |
| Template Method | Define skeleton | Processing pipelines |
| State | Change behavior | State machines |
| Chain of Responsibility | Pass along chain | Logging, auth |
| Visitor | Separate operations | AST, shapes |
Key Takeaways
Section titled “Key Takeaways”- Behavioral patterns manage object interactions
- Choose pattern based on the problem:
- Need loose coupling → Observer
- Need algorithm flexibility → Strategy
- Need undo/redo → Command
- Need state transitions → State
- Modern C++ can use std::function for simpler implementations
- Pattern overuse can add unnecessary complexity