对于这个任务,我不认为观察者模式可以用它的经典形式。已经收到请求的人和选择接受请求的人不是同一群体。那些选择接受请求的人可以被视为观察员,他们可以在任何时候停止接受请求,因此不登记为观察员,但他们仍然会收到请求。这意味着我们有两层观察者,其中一层是另一层的子集。此外,有不同的请求类型,模板枚举RequestType,并且拥有一个观察者容器将只属于一种类型的请求,这将不会完成这项工作。当然,一旦请求者的问题解决了,每个接收请求的人都会被自动删除请求,但是那些正在处理请求的人也必须停止。所以我就是这么处理的。
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);
}
}
}在这里,我只能提出我所使用的主要观点。我不能给一个完整的编译代码,因为它将是太长的适合在这里和阅读。
发布于 2022-05-11 19:11:59
使用设计模式的
对于这个任务,我不认为观察者模式不能在其经典形式中使用。
我假设双重否定是无意的。实际上,每个软件设计模式都是解决特定问题的工具。如果它不完全匹配问题,你可能不应该使用它的现状,否则你正在尝试用锤子拧紧螺丝。
那些选择接受请求的人可以被视为观察员,他们可以在任何时候停止接受请求,因此不登记为观察员,但他们仍然会收到请求。这意味着我们有两层观察者,其中一层是另一层的子集。
但不是严格的子集。它更像是两个圆圈部分重叠的Venn图,两个圆圈是:
如果我正确理解,多个观察者可能会接受相同类型的请求,但是一旦其中一个完成了请求,接受相同请求的其他观察者应该停止处理该请求。这是可以用不同方式解决的部分。
一种方法是有一些与来自主题的请求相关联的共享对象;每个观察者都获得一个指向该对象的指针。这个共享对象用于记录请求的状态;应该有一种原子方法来查询请求是否已经完成,并记录观察者已经完成了请求。
另一种方法是将每种请求类型分成两种:一种用于启动请求,另一种用于停止请求。每个观察员最初都赞同这两种意见。一旦观察者接受了请求,它就可以从“开始请求”消息中取消订阅,但是它仍然必须侦听“停止请求”消息。当一个观察者完成主题的请求时,主题将“停止请求”发送给侦听观察者,其余的观察者随后可以取消他们的工作,然后取消订阅“停止请求”消息。您可能需要使用唯一的ID来将“停止请求”消息与相应的“开始请求”消息相匹配。
const getRequester()。
您确定要有人在构造requester之后更改Request指针吗?这似乎是一件危险的事情,最好是消除非const过载的getRequester()。
auto通过使用auto,可以避免在许多情况下拼写类型名。例如,而不是:
std::unordered_map<RequestType, std::shared_ptr<Request>>::const_iterator it = requestsMade.find(FreeMeFromWeb);只需写:
auto it = requestsMade.find(FreeMeFromWeb);我还建议您在range-for循环中使用auto。除了更少的输入和更清晰的代码之外,它还可以防止某些类型或错误(不小心地使用错误的类型名称和不想要的隐式强制转换导致它在没有警告的情况下编译)。
如果可能的话,
if与初始化器一起使用由于C++17,if-statements可以在它们中包含一个初始化器。例如:
if (auto it = requestsMade.find(FreeMeFromWeb); it != requestsMade.end()) {
...
}std::shared_ptrs的低效率复制
创建一个std::shared_ptr的副本并不是免费的,因为原子引用计数需要更新,所以避免这样做。有两种方法可以避免这种情况,第一种方法是std::move() it,如下所示:
if (auto it = requestsMade.find(FreeMeFromWeb); it != requestsMade.end()) {
auto request = std::move(it->second);
requestsMade.erase(FreeMeFromWeb);
...
}然而,更简单的方法是不复制它,只需使用地图中的元素,并且只在您完成该元素之后才擦除它:
if (auto it = requestsMade.find(FreeMeFromWeb); it != requestsMade.end()) {
auto& request = it->second;
...
requestsMade.erase(FreeMeFromWeb);
}我看不到Menu::addOption()的声明,但是如果它通过值将std::shared_ptr<Option>作为参数,那么下面的声明效率很低:
const std::shared_ptr<Option> option = std::make_shared<Option>(handleRequest, handleRequest->description());
menu.addOption(option); 这可能意味着,在最后一行中将生成一个副本,我猜另一个副本将在addOption()中生成。使变量option非const允许您将其std::move()转换为addOption()。或者写这个代替:
menu.addOption(std::make_shared<Option>(handleRequest, handleRequest->description());发布于 2022-05-12 02:00:22
下面是使用G.Sliepen建议的两层观察者模式的完整编译代码。有两份单独的观察员名单,即收到请求的观察员和接受请求的观察员。所接收的请求将存储接收器,而不是存储所接收的请求。
#include <iostream>
#include <list>
#include <vector>
#include <unordered_map>
#include <memory>
struct LivingBeing;
enum RequestType { FreeMeFromWeb, HelpMeGetUp, LendMeSomeMoney, NumRequestTypes };
struct HandleRequestBase;
class Request {
protected:
LivingBeing* requester;
public:
Request (LivingBeing* r) : requester(r) { }
LivingBeing* getRequester() const { return requester; }
virtual std::shared_ptr<HandleRequestBase> makeHandleRequest (LivingBeing* helper) = 0;
virtual void registerReceivedRequestObserver (LivingBeing*) = 0;
virtual void unregisterReceivedRequestObserver (LivingBeing*) = 0;
virtual void registerAcceptedRequestObserver (LivingBeing*) = 0;
virtual void unregisterAcceptedRequestObserver (LivingBeing*) = 0;
virtual void notifyReceivedRequestObservers() = 0;
virtual void notifyAcceptedRequestObservers() = 0;
virtual void newRoundUpdate() = 0;
virtual void terminate() = 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 {
std::list<LivingBeing*> receivedRequestObservers, acceptedRequestObservers; // *** Two categories of observers needed.
static const std::string tag;
using Request::Request;
virtual ~RequestClass() { terminate(); }
virtual std::shared_ptr<HandleRequestBase> makeHandleRequest (LivingBeing* helper) override { return std::make_shared<HandleRequest<R>>(helper, this); }
virtual void registerReceivedRequestObserver (LivingBeing* being) override { receivedRequestObservers.push_back(being); }
virtual void unregisterReceivedRequestObserver (LivingBeing* being) override { receivedRequestObservers.remove(being); }
virtual void registerAcceptedRequestObserver (LivingBeing* being) override { acceptedRequestObservers.push_back(being); }
virtual void unregisterAcceptedRequestObserver (LivingBeing* being) override { acceptedRequestObservers.remove(being); }
private:
virtual void notifyReceivedRequestObservers() override;
virtual void notifyAcceptedRequestObservers() override;
virtual void terminate() override;
virtual void newRoundUpdate() override;
};
struct AskForHelpToUntangleFromWeb : Action {
LivingBeing* helper;
AskForHelpToUntangleFromWeb (LivingBeing* tangledBeing, LivingBeing* h) : Action(tangledBeing), helper(h) { }
virtual void execute() override;
};
struct LivingBeing {
std::string name;
std::vector<std::shared_ptr<Action>> menuOptions;
// std::list<std::shared_ptr<Request>> requestsReceived; // ****** We don't need this anymore if using the Observer Pattern.
// ****** Instead of the LivingBeing receivers storing the requests received, the requests received will store the LivingBeing receivers.
std::unordered_map<RequestType, std::shared_ptr<Request>> requestsMade;
LivingBeing (const std::string& name) : name(name) { }
std::string getName() const { return name; }
void receiveRequest (const std::shared_ptr<Request>& request) {
request->registerReceivedRequestObserver(this); // *** Register as a receivedRequestObserver.
}
void addMenuOption (const std::shared_ptr<Action>& action, const std::string& description) {
menuOptions.push_back(action);
std::cout << "Menu option added for " << name << ": " << description << '\n';
}
template <typename T> void removeMenuOption (LivingBeing*) { menuOptions.pop_back(); } // Keeping it simple for now. Need to search for all menu options with type T associated.
void celebrates() { std::cout << name << " celebrates. Job accomplished!\n"; }
};
template <RequestType R>
void RequestClass<R>::notifyReceivedRequestObservers() {
for (LivingBeing* observer : receivedRequestObservers)
observer->addMenuOption(std::make_shared<HandleRequest<R>>(observer, this), "Help " + requester->getName() + " untangle from the web.");
}
template <RequestType R>
void RequestClass<R>::notifyAcceptedRequestObservers() {
for (LivingBeing* observer : acceptedRequestObservers)
std::cout << observer->getName() << " is notified that " << requester->getName() << " is still entangled in the web.\n"; // Or whatever requester's current state is.
}
template <RequestType R>
void RequestClass<R>::terminate() {
for (LivingBeing* observer : receivedRequestObservers) {
observer->removeMenuOption<HandleRequest<R>>(requester); // Everyone who received the request will have the menu option of accepting the request removed.
std::cout << requester->getName() << " no longer needs " << observer->getName() << "'s help.\n";
}
for (LivingBeing* observer : acceptedRequestObservers) {
observer->celebrates();
std::cout << requester->getName() << " thanks " << observer->getName() << " for helping.\n";
}
}
template <RequestType R>
void RequestClass<R>::newRoundUpdate() {
notifyAcceptedRequestObservers();
acceptedRequestObservers.clear(); // Instead of removing an acceptedRequestObserver when he chooses to quit (which is not so straightforward to implement),
// simply remove all acceptedRequestObservers and register them back only if they choose to continue the job in the next round.
}
template <>
void HandleRequest<FreeMeFromWeb>::execute() {
static int numAttempts = 0;
std::cout << "ACTION TAKEN: " << actionTaker->getName() << " helps " << request->getRequester()->getName() << " untangle him from the web.\n\n";
request->registerAcceptedRequestObserver(actionTaker); // *** Register as an acceptedRequestObserver.
numAttempts++;
if (numAttempts >= 4) { // Let's assume the attempt succeeds after 4 tries.
std::cout << actionTaker->getName() << " has succeeded! " << request->getRequester()->getName() << " is now free from the web.\n";
request->getRequester()->requestsMade.erase(FreeMeFromWeb); // RequestClass<FreeMeFromWeb>::terminate() called through its destructor.
}
}
void AskForHelpToUntangleFromWeb::execute() {
std::cout << "ACTION TAKEN: " << actionTaker->getName() << " asks " << helper->getName() << " to help untangle him from the web.\n\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);
}
std::list<LivingBeing*> allBeingsPresent;
void newRound() {
static int roundNumber = 0;
std::cout << "Round " << ++roundNumber << '\n';
for (LivingBeing* being : allBeingsPresent) {
for (auto& pair : being->requestsMade)
pair.second->newRoundUpdate(); // Every request (of any type) from every member of allBeingsPresent calls newRoundUpdate().
}
}
int main() {
LivingBeing *goblin = new LivingBeing("Ugly Goblin"), *borg = new LivingBeing("Borg"), *rex = new LivingBeing("Rex"), *merlin = new LivingBeing("Merlin");
allBeingsPresent = {goblin, borg, rex, merlin};
AskForHelpToUntangleFromWeb askBorgForHelp(goblin, borg);
AskForHelpToUntangleFromWeb askRexForHelp(goblin, rex);
AskForHelpToUntangleFromWeb askMerlinForHelp(goblin, merlin);
newRound();
askBorgForHelp.execute(); askRexForHelp.execute(); askMerlinForHelp.execute(); // Assume these menu options were chosen by 'goblin'.
goblin->requestsMade.at(FreeMeFromWeb)->notifyReceivedRequestObservers(); // The request itself must be stored somewhere, in this case, in the 'goblin->requestsMade' map (with FreeMeFromWeb as key).
std::cout << "borg->menuOptions.size() = " << borg->menuOptions.size() << '\n'; // Menu option of helping 'goblin' added.
std::cout << "rex->menuOptions.size() = " << rex->menuOptions.size() << '\n'; // Menu option of helping 'goblin' added.
std::cout << "merlin->menuOptions.size() = " << merlin->menuOptions.size() << '\n'; // Menu option of helping 'goblin' added.
borg->menuOptions.back()->execute(); // Assume 'borg' chooses this menu option, to help 'goblin'.
merlin->menuOptions.back()->execute(); // Assume 'merlin' chooses this menu option, to help 'goblin'.
newRound();
borg->menuOptions.back()->execute(); // 'borg' chooses to continue helping 'goblin' (but 'merlin' does not, though he still has the option to).
rex->menuOptions.back()->execute(); // 'rex' chooses to start helping 'goblin'.
// rex succeeds, but only borg and rex celebrate, since merlin stopped helping.
std::cout << "borg->menuOptions.size() = " << borg->menuOptions.size() << '\n'; // Menu option of helping 'goblin' removed.
std::cout << "rex->menuOptions.size() = " << rex->menuOptions.size() << '\n'; // Menu option of helping 'goblin' removed.
std::cout << "merlin->menuOptions.size() = " << merlin->menuOptions.size() << '\n'; // Menu option of helping 'goblin' removed.
std::cin.get();
}输出:
Round 1
ACTION TAKEN: Ugly Goblin asks Borg to help untangle him from the web.
ACTION TAKEN: Ugly Goblin asks Rex to help untangle him from the web.
ACTION TAKEN: Ugly Goblin asks Merlin to help untangle him from the web.
Menu option added for Borg: Help Ugly Goblin untangle from the web.
Menu option added for Rex: Help Ugly Goblin untangle from the web.
Menu option added for Merlin: Help Ugly Goblin untangle from the web.
borg->menuOptions.size() = 1
rex->menuOptions.size() = 1
merlin->menuOptions.size() = 1
ACTION TAKEN: Borg helps Ugly Goblin untangle him from the web.
ACTION TAKEN: Merlin helps Ugly Goblin untangle him from the web.
Round 2
Borg is notified that Ugly Goblin is still entangled in the web.
Merlin is notified that Ugly Goblin is still entangled in the web.
ACTION TAKEN: Borg helps Ugly Goblin untangle him from the web.
ACTION TAKEN: Rex helps Ugly Goblin untangle him from the web.
Rex has succeeded! Ugly Goblin is now free from the web.
Ugly Goblin no longer needs Borg's help.
Ugly Goblin no longer needs Rex's help.
Ugly Goblin no longer needs Merlin's help.
Borg celebrates. Job accomplished!
Ugly Goblin thanks Borg for helping.
Rex celebrates. Job accomplished!
Ugly Goblin thanks Rex for helping.
borg->menuOptions.size() = 0
rex->menuOptions.size() = 0
merlin->menuOptions.size() = 0https://codereview.stackexchange.com/questions/276457
复制相似问题