Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ set(APP_SOURCES
"src/patterns/behavioral/Memento.cpp"
"src/patterns/behavioral/Visitor.cpp"
"src/patterns/behavioral/TemplateMethod.cpp"
"src/patterns/behavioral/Strategy.cpp"
"src/patterns/behavioral/State.cpp"
"src/patterns/behavioral/Observer.cpp"
)

# Test files
Expand Down
4 changes: 4 additions & 0 deletions docs/uml/patterns_behavioral_observer.drawio.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions docs/uml/patterns_behavioral_state.drawio.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions docs/uml/patterns_behavioral_strategy.drawio.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
204 changes: 204 additions & 0 deletions src/patterns/behavioral/Observer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
// Observer is a behavioral design pattern that lets you define a subscription mechanism
// to notify multiple objects about any events that happen to the object they’re observing.
// Usage examples: The most popular usage of the Observer pattern in C++ code is facilitating communications between GUI components of an app.
// The synonym of the Observer is the `Controller` part of MVC pattern.
// Appicability:
// (*) when changes to the state of one object may require changing other objects, and the actual set of objects is unknown beforehand or changes dynamically.
// (**) when some objects in your app must observe others, but only for a limited time or in specific cases.

// UML: docs/uml/patterns_behavioral_observer.drawio.svg

#include <iostream>
#include <string>
#include <list>

namespace
{
namespace Observer
{
enum class Event
{
CREATE = 0,
READ,
UPDATE,
DELETE,
};

static inline const char *getEventName(const Event &e)
{
switch (e)
{
case Event::CREATE:
return "CREATE";
case Event::READ:
return "READ";
case Event::UPDATE:
return "UPDATE";
case Event::DELETE:
return "DELETE";
}
return "invalid_event";
}

/**
* IObserver aka Subscriber
* The Subscriber interface declares the notification interface.
* In most cases, it consists of a single update method.
* The method may have several parameters that let the publisher pass some event details along with the update.
* E.g. Event Listen to UI events
*/
class IListenerObserver
{
public:
virtual ~IListenerObserver() = default;

// update
virtual void update(const Event e) = 0;
};

/**
* Subject aka Publisher
* The Publisher issues events of interest to other objects.
* These events occur when the publisher changes its state or executes some behaviors.
* Publishers contain a subscription infrastructure that lets new subscribers join and current subscribers leave the list.
*
* E.g Widget dispatches click events to observers
*/
class IWidgetSubject
{
public:
virtual ~IWidgetSubject() {};
// addListener
virtual void attach(IListenerObserver *observer) = 0;
// removeLister
virtual void detach(IListenerObserver *observer) = 0;
// e.g.click
virtual void notify(const Event &e) = 0;
};

class ButtonConcreteSubject : public IWidgetSubject
{
private:
std::list<IListenerObserver *> m_listeners;

public:
void attach(IListenerObserver *observer) override
{
m_listeners.push_back(observer);
}

void detach(IListenerObserver *observer) override
{
m_listeners.remove(observer);
}

void notify(const Event &e) override
{
std::cout << "[Subject] notify event-" << getEventName(e) << "\n";
for (IListenerObserver *o : m_listeners)
{
o->update(e);
}
}
};

class AbstractListenerObserver : public IListenerObserver
{
private:
int m_num;
inline static int num_observers = 0;

protected:
void log(const Event &e) const
{
std::cout << "\t-id:" << m_num << "-event:" << getEventName(e) << "\n";
}

public:
explicit AbstractListenerObserver()
{
m_num = ++num_observers;
}
};

/**
* Concrete Subscribers perform some actions in response to notifications issued by the publisher.
* All of these classes must implement the same interface so the publisher isn’t coupled to concrete classes.
*/
class ConcreteListenerObserverA : public AbstractListenerObserver
{
private:
static const inline char *type = "A-type";

public:
void update(const Event e) override
{
std::cout << "\tListener: " << type;
log(e);
}
};

class ConcreteListenerObserverB : public AbstractListenerObserver
{
private:
static const inline char *type = "B-type";

public:
void update(const Event e) override
{
std::cout << "\tListener: " << type;
log(e);
}
};

/**
* The Client creates publisher and subscriber objects separately
* and then registers subscribers for publisher updates.
*/
namespace Client
{
void clientCode(IWidgetSubject *const s)
{
s->notify(Event::UPDATE);
}
}

void run()
{
IWidgetSubject *btn = new ButtonConcreteSubject();

IListenerObserver *listener_1 = new ConcreteListenerObserverA();
IListenerObserver *listener_2 = new ConcreteListenerObserverA();
IListenerObserver *listener_3 = new ConcreteListenerObserverA();
IListenerObserver *listener_4 = new ConcreteListenerObserverB();

btn->attach(listener_1);
btn->attach(listener_2);
btn->attach(listener_3);
btn->attach(listener_4);
Client::clientCode(btn);

std::cout << "Remove listener2\n";
btn->detach(listener_2);
Client::clientCode(btn);

delete btn;
delete listener_1;
delete listener_2;
delete listener_3;
delete listener_4;
}

}
}

struct ObserverAutoRunner
{
ObserverAutoRunner()
{
std::cout << "\n--- Observer Pattern Example ---\n";
Observer::run();
}
};

static ObserverAutoRunner instance;
170 changes: 170 additions & 0 deletions src/patterns/behavioral/State.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// State is a behavioral design pattern that lets an object alter its behavior when its internal state changes.
// It appears as if the object changed its class.
// Appicability:
// (*) when you have an object that behaves differently depending on its current state
// , the number of states is enormous, and the state-specific code changes frequently.
// (**) when you have a class polluted with massive conditionals that alter
// how the class behaves according to the current values of the class’s fields.
// (***) when you have a lot of duplicate code across similar states and transitions of a condition-based state machine.
// UML: docs/uml/patterns_behavioral_state.drawio.svg

#include <iostream>
#include <string>
namespace
{
namespace State
{
class DeviceContext;

/**
* The State interface declares the state-specific methods.
* These methods should make sense for all concrete states because you don’t want some of your states
* to have useless methods that will never be called.
*/
class IState
{
public:
virtual ~IState() = default;
virtual void setContext(DeviceContext *ctx) = 0;
virtual void handle() = 0;
};

/*
* To avoid duplication of similar code across multiple states, you may provide intermediate abstract classes
* that encapsulate some common behavior.
*/
class AbstractState : public IState
{
protected:
DeviceContext *m_ctx;

public:
void setContext(DeviceContext *ctx) override
{
this->m_ctx = ctx;
}
};

/**
* Context stores a reference to one of the concrete state objects and delegates to it all state-specific work.
* The context communicates with the state object via the state interface.
* The context exposes a setter for passing it a new state object.
*/
class DeviceContext
{
private:
IState *m_state;

public:
explicit DeviceContext(IState *state) : m_state{nullptr}
{
this->changeState(state);
}

~DeviceContext()
{
delete m_state;
}

void changeState(IState *state)
{
std::cout << "[DeviceContext]: Changing state\n";
if (this->m_state != nullptr)
{
delete this->m_state;
}
this->m_state = state;
this->m_state->setContext(this);
}

void operation()
{
this->m_state->handle();
}
};

/**
* Concrete States provide their own implementations for the state-specific methods.
*/
class IdeConcreteState : public AbstractState
{
public:
void handle() override;
};

class ProcessingConcreteState : public AbstractState
{
public:
void handle() override;
};

class ErrorConcreteState : public AbstractState
{
public:
void handle() override
{
std::cout << "[Error] Device error! Reset required.\n";

// After recover => go Idle
this->m_ctx->changeState(new IdeConcreteState());
}
};

void IdeConcreteState::handle()
{
std::cout << "[Ide] Device is idle. Waiting...\n";
this->m_ctx->changeState(new ProcessingConcreteState());
}

void ProcessingConcreteState::handle()
{
std::cout << "[Processing] Processing data...\n";
bool ok = true; // Example processing result
static int index = 0;
index++;
if (index % 2 == 0)
{
ok = true;
}
else
{
ok = false;
}
if (ok)
{
// Back to idle after finishing job
this->m_ctx->changeState(new IdeConcreteState());
}
else
{
this->m_ctx->changeState(new ErrorConcreteState());
}
}

namespace Client
{
void clientCode(DeviceContext *const device)
{
device->operation();
}
}
void run()
{
DeviceContext *device = new DeviceContext(new IdeConcreteState());
for (int loopIdx = 0; loopIdx < 10; ++loopIdx)
Client::clientCode(device);
delete device;
}
}
}

struct StateAutoRunner
{
StateAutoRunner()
{
std::cout << "\n--- State Pattern Example ---\n";
State::run();
}
};

static StateAutoRunner instance;
Loading