For this task, I don't think that the Observer Pattern can be used in its classic form. Those who have received the requests and those who choose to accept the request are not the same group. Those who choose to accept
the request could be viewed as the observers, and they can stop accepting the request at any time and so be unregistered as observers, but they will still have the request received. This means that we have
two layers of observers, with one being a subset of another. Furthermore, there are different requests types, with template enum RequestType
, and having a container of observers will only pertain to one type of request,
which won't do the job. Of course, once the requester's problem is solved, everybody who received the request will have the request automatically removed, but those who are in the middle of handling the request must be made to stop too. So this is how I handled it.
enum RequestType { FreeMeFromWeb, HelpMeGetUp };
struct HandleRequestBase;
class Request {
protected:
LivingBeing* requester;
public:
Request (LivingBeing* r) : requester(r) { }
LivingBeing* getRequester() const { return requester; }
LivingBeing*& getRequester() { return requester; }
virtual std::shared_ptr<HandleRequestBase> makeHandleRequest (LivingBeing* helper) = 0;
};
class Action {
protected:
LivingBeing* actionTaker;
public:
Action (LivingBeing* being) : actionTaker(being) { }
virtual ~Action() = default;
virtual void execute() = 0;
};
struct HandleRequestBase : Action {
Request* request;
HandleRequestBase (LivingBeing* helper, Request* r) : Action(helper), request(r) {}
virtual ~HandleRequestBase() = default;
};
template <RequestType>
struct HandleRequest : HandleRequestBase {
using HandleRequestBase::HandleRequestBase;
virtual void execute() override;
};
template <RequestType R>
struct RequestClass : Request {
static const std::string tag;
using Request::Request;
virtual std::shared_ptr<HandleRequestBase> makeHandleRequest (LivingBeing* helper) override { return std::make_shared<HandleRequest<R>>(helper, this); }
};
template <> const std::string RequestClass<FreeMeFromWeb>::tag = "FreeMeFromWebRequest";
template <> const std::string RequestClass<HelpMeGetUp>::tag = "HelpMeGetUpRequest";
class LivingBeing {
std::list<std::shared_ptr<Request>> requestsReceived;
std::unordered_map<RequestType, std::shared_ptr<Request>> requestsMade;
...
const std::list<std::shared_ptr<Request>>& getRequestsReceived() const { return requestsReceived; }
void receiveRequest (const std::shared_ptr<Request>& request) { requestsReceived.push_back(request); }
void removeRequestReceived (const std::shared_ptr<Request>& request) { requestsReceived.remove(request); }
private:
virtual Menu buildMenu();
};
template <> void HandleRequest<FreeMeFromWeb>::execute() {
std::cout << "\nACTION TAKEN: " << actionTaker->getName() << " helps " << request->requester->getName() << " untangle him from the web.\n";
std::cout << "It may take some time.\n";
}
inline void LivingBeing::removeCaughtInWebSpellState() {
std::unordered_map<RequestType, std::shared_ptr<Request>>::const_iterator it = requestsMade.find(FreeMeFromWeb);
std::shared_ptr<Request> request = it != requestsMade.end() ? it->second : nullptr;
if (request)
requestsMade.erase(FreeMeFromWeb); // 'this' removes his request made to untangle from the web now that he no longer needs the help, whether the request was followed by anyone or not.
std::cout << name << " is now free from the web.\n";
if (!request)
return;
for (LivingBeing* being : allBeingsPresent) { // Anyone who might have been helping 'requester' to untangle from the web shall celebrate.
auto it = std::find(being->getRequestsReceived().begin(), being->getRequestsReceived().end(), request);
if (it != being->getRequestsReceived().end())
being->celebrate();
}
for (LivingBeing* being : allBeingsPresent)
being->removeRequestReceived(request); // Everyone present calls 'removeRequestReceived(request)' in case others received the same request (for those who didn't receive the request, then nothing will happen for them).
}
// Within, StatesMediator::buildMenu(), there is:
if (!subject->getRequestsReceived().empty()) { // 'subject' is the LivingBeing that the menu of actions is being built for.
for (const std::shared_ptr<Request>& request : subject->getRequestsReceived()) {
const std::shared_ptr<HandleRequestBase> handleRequest = request->makeHandleRequest(subject);
const std::shared_ptr<Option> option = std::make_shared<Option>(handleRequest, handleRequest->description());
menu.addOption(option);
}
}
// Then there is (AskForHelpToUntangleFromWeb derives from Action):
void AskForHelpToUntangleFromWeb::execute() {
std::cout << "\nACTION TAKEN: " << actionTaker->getName() << " asks " << helper->getName() << " to help untangle him from the web.\n";
auto it = actionTaker->requestsMade.find(FreeMeFromWeb);
if (it == actionTaker->requestsMade.end()) {
std::shared_ptr<RequestClass<FreeMeFromWeb>> request = std::make_shared<RequestClass<FreeMeFromWeb>>(actionTaker);
actionTaker->requestsMade[FreeMeFromWeb] = request;
helper->receiveRequest(request);
}
else
helper->receiveRequest(it->second);
}
// And within Menu StatesMediator::buildMenu(), there is:
for (LivingBeing* being : allBeingsPresent) {
if (caughtInWebSpellState->getHelpers().empty()) {
for (LivingBeing* being : allBeingsPresent) {
if (being == subject || being->isDead() || being->isIncapacitated())
continue;
auto it = subject->requestsMade.find(FreeMeFromWeb);
if (it != subject->requestsMade.end()) { // i.e. 'subject' has already requested for help to get out of the web.
std::shared_ptr<Request> request = it->second;
auto j = std::find(being->getRequestsReceived().begin(), being->getRequestsReceived().end(), request);
if (j != being->getRequestsReceived().end()) // 'being' is already helping 'subject', so no need for 'subject' to ask 'being' again (even if 'being' stopped helping 'subject', 'being' still has the request from 'subject' in his menu of options).
continue;
}
std::shared_ptr<Option> option = std::make_shared<Option>(std::make_shared<AskForHelpToUntangleFromWeb>(subject, being), "Ask " + being->getName() + " for help to free him from the web.");
menu.addOption(option);
}
}
}
Here I could only illlustrate the main ideas I used. I cannot give a fully compiling code as it would be too long fit in here and to read.