首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用std::any的责任链模式

使用std::any的责任链模式
EN

Code Review用户
提问于 2023-02-20 00:54:54
回答 3查看 83关注 0票数 2

下面的代码有一个Handler类,它遵循经典的责任链模式。但是我们不想为我们想要处理的每一个新类型编写一个新的处理程序类。我试图使Handler成为一个模板类,但它无法工作,因为类Worker是从Handler派生的,因此我求助于使用std::any,现在Handler类一般地处理任何类型。此代码的输出显示了Worker及其派生类型,处理类型为DishInventorySpeechToMedia的对象。责任链本身已经被允许在从CEODishwasher的级别或从DishwasherCEO的级别上工作,而从招聘过程中预先建立的链被认为已经发生了。

代码语言:javascript
复制
#include 
#include 
#include 
#include 
#include 
#include 
#include 

class Handler {
protected:
    Handler* next = nullptr;
    std::any objectHandled;
public:
    void setNext (Handler* handler) { next = handler; }
    virtual void handle() {
        if (next)
            next->handle();  // handle() is meant to be overriden, as per the Chain of Responsibility pattern.
    }
    void resetObjectHandled() { objectHandled.reset(); }
protected:
    template  void passObjectHandled (T& t) {
        next->objectHandled = t;
        Handler::handle();  // Do not override!  The other key line in the Chain of Responsibility pattern within the each handle() override.
        next = nullptr;
    }
    template  void passObjectHandledAndReset (T& t) {
        passObjectHandled(t);
        objectHandled.reset();
    }
};

class Person {
    std::string name;
public:
    Person (const std::string& n) : name(n) { }
    std::string getName() const { return name; }
};

class Dish {
    bool isClean;
public:
    Dish (bool b = true) : isClean(b) { }
    void getsCleaned() { isClean = true;  std::cout << "Dirty dish got cleaned.\n"; }
    bool getIsClean() const { return isClean; }
};

class Inventory {
    bool isDone = false;
public:
    void getsDone() { isDone = true; }
    bool getIsDone() const { return isDone; }
};

class SpeechToMedia {
    bool delivered = false;
public:
    bool showDeliveredStatus() const { return delivered; }
    void isDelivered() { delivered = true; }
};

class Worker : public Person, public Handler {  // Other derived types of Handler will set up their chains in their own way.
protected:
    std::vector subordinates;
    Worker* boss = nullptr;
public:
    using Person::Person;
    void addSubordinate (Worker* w) { subordinates.push_back(w);  w->setBoss(this); }
    void setBoss (Worker* w) { boss = w; }
    virtual void stumbleUpon (Dish*) = 0;
    virtual void stumbleUpon (Inventory*) = 0;
    virtual void stumbleUpon (SpeechToMedia*) = 0;
    void setNextHandlerChainAmongSubordinates() {  // Choose next Handler* to be whichever subordinate, if any, happens to be physically closest.
        if (!subordinates.empty()) {
            Worker* subordinateClosest = findClosestSubordinate();
            setNext(subordinateClosest);
            subordinateClosest->setNextHandlerChainAmongSubordinates();
        }
    }
    void setNextHandlerChainAmongBosses() {
        if (boss) {  // Even though each worker has only one (immediate) boss, the entire chain should be set all at once just as setNextHandlerChainAmongSubordinates() does.
            next = boss;
            boss->setNextHandlerChainAmongBosses();
        }
    }
private:
    Worker* findClosestSubordinate() { return subordinates.back(); }  // Keeping it simple for now.
};

class Company;

class CEO : public Worker {
    Company* company;
    struct AnyVisitor {
        static const std::unordered_map> map;
    };
public:
    using Worker::Worker;
    CEO (const std::string& name);
    Company* getCompany() { return company; }
    void stumbleUpon (Dish* dirtyDish) override {
        std::cout << getName() << " spots a dirty dish.  The washing order is passed on to the nearest manager.\n";
        setNextHandlerChainAmongSubordinates();
        passObjectHandled(dirtyDish);
    }
    void stumbleUpon (Inventory* inventory) override {
        std::cout << getName() << " passes the inventory paper work to the nearest manager.\n";
        setNextHandlerChainAmongSubordinates();
        passObjectHandled(inventory);
    }
    void stumbleUpon (SpeechToMedia* speech) override {
        std::cout << getName() << " speaks to the media.\n";
        speech->isDelivered();  
    }
    void handle() override {
        const auto it = AnyVisitor::map.find(std::type_index(objectHandled.type()));
        if (it != AnyVisitor::map.end())
            (it->second)(this);
        else
            Handler::handle();
    }
    Worker* getManager(int n = 0) const { return subordinates[n]; }
};
const std::unordered_map> CEO::AnyVisitor::map {
    {std::type_index(typeid(SpeechToMedia*)), [](CEO* ceo) {
            std::cout << ceo->getName() << " speaks to the media.\n";
            std::any_cast(ceo->objectHandled)->isDelivered();
            ceo->resetObjectHandled();
        }
    }
};

class Manager : public Worker {
    struct AnyVisitor {
        static const std::unordered_map> map;
    };
public:
    using Worker::Worker;
    void stumbleUpon (Dish* dirtyDish) override {
        std::cout << getName() << " spots a dirty dish.  The washing order is passed to the nearest supervisor.\n";
        setNextHandlerChainAmongSubordinates();
        passObjectHandled(dirtyDish);
    }
    void stumbleUpon (Inventory* inventory) override {
        std::cout << getName() << " passes the inventory paper work to the nearest supervisor.\n";
        setNextHandlerChainAmongSubordinates();
        passObjectHandled(inventory);
    }
    void stumbleUpon (SpeechToMedia* speech) override {
        std::cout << getName() << " does not speak to the media.\n";
        setNextHandlerChainAmongBosses();  // Going up the ranks instead of going down the ranks.
        passObjectHandled(speech);
    }
    void handle() override {
        const auto it = AnyVisitor::map.find(std::type_index(objectHandled.type()));
        if (it != AnyVisitor::map.end())
            (it->second)(this);
        else
            std::cout << "Error! " << objectHandled.type().name() << " not registered in ManagerAnyVisitor::map.\n";
    }
    Worker* getSupervisor(int n = 0) const { return subordinates[n]; }
};

// Dish*, Inventory*, SpeechToMedia* are (so far) the types stored in std::any for Worker subtypes, but other derived classes of
// Handler may store other types, so std::variant would not work for Handler's 'objectHandled' data member.  Hence std::any is used.
const std::unordered_map> Manager::AnyVisitor::map {
    {std::type_index(typeid(Dish*)), [](Manager* manager) {
            std::cout << manager->getName() << " does not deal with dirty dishes.  The dirty dish is passed to the nearest supervisor\n";
            manager->passObjectHandledAndReset(manager->objectHandled);
        }
    },
    {std::type_index(typeid(Inventory*)), [](Manager* manager) {
            std::cout << manager->getName() << " does not deal with inventory.  The inventory paper work is passed to the nearest supervisor.\n";
            manager->passObjectHandledAndReset(manager->objectHandled);
        }
    },
    {std::type_index(typeid(SpeechToMedia*)), [](Manager* manager) {
            std::cout << manager->getName() << " does not speak to the media.  The speech paper is passed to the CEO.\n";
            manager->passObjectHandledAndReset(manager->objectHandled);
        }
    }
};

class Supervisor : public Worker {
    struct AnyVisitor {
        static const std::unordered_map> map;
    };
public:
    using Worker::Worker;
    void stumbleUpon (Dish* dirtyDish) override {
        std::cout << getName() << " spots a dirty dish.  The washing order is passed to the nearest dishwasher.\n";
        setNextHandlerChainAmongSubordinates();
        passObjectHandled(dirtyDish);
    }
    void stumbleUpon (Inventory* inventory) override {
        std::cout << getName() << " takes care of the inventory paper work.\n";
        fillOutInventory(inventory);
    }
    void stumbleUpon (SpeechToMedia* speech) override {
        std::cout << getName() << " does not speak to the media.\n";
        setNextHandlerChainAmongBosses();
        passObjectHandled(speech);
    }
    void handle() override {
        const auto it = AnyVisitor::map.find(std::type_index(objectHandled.type()));
        if (it != AnyVisitor::map.end())
            (it->second)(this);
        else
            std::cout << "Error! " << objectHandled.type().name() << " not registered in SupervisorAnyVisitor::map.\n";
    }
    void fillOutInventory (Inventory* inventory) { inventory->getsDone(); }
    Worker* getDishwasher(int n = 0) const { return subordinates[n]; }
};
const std::unordered_map> Supervisor::AnyVisitor::map {
    {std::type_index(typeid(Dish*)), [](Supervisor* supervisor) {
            std::cout << supervisor->getName() << " does not deal with dirty dishes.  The dirty dish is passed to the nearest dishwasher.\n";
            supervisor->passObjectHandledAndReset(supervisor->objectHandled);
        }
    },
    {std::type_index(typeid(Inventory*)), [](Supervisor* supervisor) {
            std::cout << supervisor->getName() << " takes care of the inventory paper work.\n";
            supervisor->fillOutInventory(std::any_cast(supervisor->objectHandled));
            supervisor->resetObjectHandled();
        }
    },
    {std::type_index(typeid(SpeechToMedia*)), [](Supervisor* supervisor) {
            std::cout << supervisor->getName() << " does not speak to the media.  The speech paper is passed to his manager boss.\n";
            supervisor->passObjectHandledAndReset(supervisor->objectHandled);
        }
    }
};

class Dishwasher : public Worker {
public:
    using Worker::Worker;
    void stumbleUpon (Dish*) override { std::cout << getName() << " cleans the dirty dish.\n"; }
    void handle() override {
        std::cout << getName() << " cleans the dirty dish.\n";
        std::any_cast(objectHandled)->getsCleaned();
        resetObjectHandled();
    }
    void stumbleUpon (Inventory* inventory) override {
        std::cout << getName() << " does not understand inventory work, and gives it to his supervisor.\n";
        setNextHandlerChainAmongBosses();
        passObjectHandled(inventory);
    }
    void stumbleUpon (SpeechToMedia* speech) override {
        std::cout << getName() << " does not speak to the media.  The speech paper is passed to his supervisor.\n";
        setNextHandlerChainAmongBosses();
        passObjectHandled(speech);
    }
};

class Company {
    CEO* ceo;
public:
    Company (CEO* c) : ceo(c) {
        for (int i = 0;  i < 3;  ++i) {
            Manager* manager = new Manager("Manager #" + std::to_string(i+1));
            for (int j = 0;  j < 4;  ++j) {
                Supervisor* supervisor = new Supervisor("Supervisor #" + std::to_string(i+1));
                manager->addSubordinate(supervisor);
                for (int k = 0;  k < 5;  ++k)
                    supervisor->addSubordinate(new Dishwasher("Dishwasher #" + std::to_string(i+1)));
            }
            ceo->addSubordinate(manager);
        }
    }
    // Destructor shall not delete the workers since they should continue to exist after the company is gone (including the CEO).
    // In fact, Company's default constructor will not create the workers, but the workers will join the company.
    CEO* getCEO() const { return ceo; }
    Worker* getManager (int a = 0) { return ceo->getManager(a); }
    Worker* getSupervisor (int a = 0, int b = 0) { return dynamic_cast(ceo->getManager(a))->getSupervisor(b); }
    Worker* getDishwasher (int a = 0, int b = 0, int c = 0) { return dynamic_cast(dynamic_cast(ceo->getManager(a))->getSupervisor(b))->getDishwasher(c); }
};

CEO::CEO (const std::string& name) : Worker(name), company(new Company(this)) { }

int main() {
    CEO ceo("CEO");
    Company& company = *ceo.getCompany();
    Dish dirtyDish(false), secondDirtyDish(false);
    ceo.stumbleUpon(&dirtyDish);
    std::cout << "Dish is clean: " << std::boolalpha << dirtyDish.getIsClean() << "\n\n";
    company.getManager(1)->stumbleUpon(&secondDirtyDish);
    std::cout << "Second dish is clean: " << std::boolalpha << secondDirtyDish.getIsClean() << "\n\n";
    
    Inventory inventory, secondInventory;
    ceo.stumbleUpon(&inventory);
    std::cout << "Inventory is done: " << std::boolalpha << inventory.getIsDone() << "\n\n";
    company.getDishwasher(1,2,3)->stumbleUpon(&secondInventory);
    std::cout << "Second inventory is done: " << std::boolalpha << secondInventory.getIsDone() << "\n\n";
    
    SpeechToMedia speech;
    company.getDishwasher(1,2,3)->stumbleUpon(&speech);
    std::cout << "Speech is delivered:  " << std::boolalpha << speech.showDeliveredStatus() << '\n';
}

输出:

代码语言:javascript
复制
CEO spots a dirty dish.  The washing order is passed on to the nearest manager.
Manager #3 does not deal with dirty dishes.  The dirty dish is passed to the nearest supervisor.
Supervisor #3 does not deal with dirty dishes.  The dirty dish is passed to the nearest dishwasher.
Dishwasher #3 cleans the dirty dish.
Dirty dish got cleaned.
Dish is clean: true

Manager #2 spots a dirty dish.  The washing order is passed to the nearest supervisor.
Supervisor #2 does not deal with dirty dishes.  The dirty dish is passed to the nearest dishwasher.
Dishwasher #2 cleans the dirty dish.
Dirty dish got cleaned.
Second dish is clean: true

CEO passes the inventory paper work to the nearest manager.
Manager #3 does not deal with inventory.  The inventory paper work is passed to the nearest supervisor.
Supervisor #3 takes care of the inventory paper work.
Inventory is done: true

Dishwasher #2 does not understand inventory work, and gives it to his supervisor.
Supervisor #2 takes care of the inventory paper work.
Second inventory is done: true

Dishwasher #2 does not speak to the media.  The speech paper is passed to his supervisor.
Supervisor #2 does not speak to the media.  The speech paper is passed to his manager boss.
Manager #2 does not speak to the media.  The speech paper is passed to the CEO.
CEO speaks to the media.
Speech is delivered:  true
EN

回答 3

Code Review用户

发布于 2023-03-17 22:51:23

责任链模式处理的是一个请求,而不是一堆请求。必须处理DishesInventorySpeech使您的代码变得杂乱无章,并增加了复杂性。

您编写的示例也不代表真正的软件工程问题,这进一步加剧了对该模式的使用的混淆。在典型的责任链模式的实现中,应该有3个独立的接口,即WashDishInterfaceMaintainInventoryInterfaceGiveSpeechInterface。然后您的CeoManagerSupervisorDishwasher可以像这样实现接口。

代码语言:javascript
复制
class Dish{};

class WashDishesInterface {
public:

    virtual void washDish(Dish & dish)
    {
        if(!mNext)
            mNext->washDish(dish);
    }

    void setNext(WashDishesInterface* next)
    {
        mNext = next;
    }

private:

    WashDishesInterface* mNext;

};

class Speech {};

class GiveSpeechInterface {
public:

    virtual void giveSpeech(Speech & speech)
    {
        if(!mNext)
            mNext->giveSpeech(speech);
    }

    void setNext(GiveSpeechInterface* next)
    {
        mNext = next;
    }

private:

    GiveSpeechInterface* mNext;

};

class Ceo : public GiveSpeechInterface, WashDishesInterface
{
public:

    void giveSpeech(Speech &) override
    {
        std::cout << "Ceo gave a speech" << std::endl;
    }

private:
};

class Dishwasher : public GiveSpeechInterface, WashDishesInterface
{
public:

    void washDish(Dish &) override
    {
        std::cout << "Dishwasher washed a dish" << std::endl;
    }

private:
};

总评

  • 使用适当的间距使代码更具可读性。很难知道Supervior类在哪里结束,Supervisor::AnyVisitor::map初始化是从哪里开始的。
  • 使用一些命名约定将成员变量与函数局部变量分开。这样可以防止变量阴影。
  • delete一切你的new。或者更好地使用智能指针。智能指针还使您能够表示所有权。拥有智能指针成员的类拥有它所指向的对象。具有原始指针成员的类引用对象,但不负责对象的生存期

Handler

  • 我不认为有任何必要有objectHandled成员变量。相反,您应该让handle函数接受std::any,并去掉创建对象副本并将其存储在Handler中的额外步骤。

Worker

  • worker类维护Workers作为下属的向量。这很容易出现use-after-free未定义的行为。要么使用单独的类来管理公司层次结构,要么使用std::weak_ptr。与boss记忆变量相同
  • 每次您必须处理某件事情时都必须调用setNextHandlerChainAmongSubordinates()方法,这很容易出错。此外,它还强制一个可以标记为const的方法为非const。当Dishwasher清洗Dish时,唯一真正改变的是盘子的状态,而不是洗碗机。同样适用于setNextHandlerChainAmongBosses()
  • 我不知道你为什么需要一个stumbleUpon()方法。我可以看出,在现实世界中,首席执行官可能会无意中遇到一道菜,并将任务委托给他人。但是,在软件开发中,对象不会偶然发现一些东西。这破坏了Single Responsiblilty Principle,现在您的Worker类负责两件事情--遇到请求和处理请求。这设计太糟糕了。

Ceo

  • 没有必要重写您使用的责任链模式的stumbleUpon()方法。只需将您偶然发现的对象传递给您的handle()方法,让它知道如何处理它。
  • 调用代码不可能知道workers.size()getManager()函数调用未定义的行为(如果n => workers.size() )。因此,如果不调用未定义的行为,则无法使用此函数。使用worker.at(n)进行边界检查。或者更好的是将getManager(int n)更改为std::vector const getManagers(),让调用代码确定它想要哪个管理器。
票数 2
EN

Code Review用户

发布于 2023-03-18 12:33:26

票数 2
EN

Code Review用户

发布于 2023-02-20 19:35:06

您的方法有几个问题。我认为这源于这样一个事实:在一些示例代码中看到了责任链的实现方式,并注意到它使用基类的appendNext()SetNext()成员函数(如维基百科文章)设置下一个处理程序,然后希望使用它来更改处理对象时遍历链的顺序。然而,在构建静态责任链时,这些成员函数只需调用一次。如果您希望它是动态的,那么我根本就不会在setNext()中使用Handler。相反,您可以编写如下的派生类:

代码语言:javascript
复制
class Worker: public Person {
    …
    Worker* getClosestSubordinate() {
        return subordinates.back();
    }

    Worder* getBoss() {
        return boss;
    }
    …
};
    
class CEO: public Worker {
    …
    void stumbleUpon(Dish* dirtyDish) override {
        std::cout << getName() << " spots a dirty dish.  The washing order is passed on to the nearest manager.\n";
        getClosestSubordinate()->stumbleUpon(dirtyDish);
    }

    void stumbleUpon(SpeechToMedia* speech) override {
        std::cout << getName() << " speaks to the media.\n";
        speech->isDelivered();  
    }
};
…
class Dishwasher: public Worker {
    …
    void stumbleUpon(Dish*) override {
        std::cout << getName() << " cleans the dirty dish.\n";
        dish->getsCleaned();
    }

    void stumbleUpon(SpeechToMedia* speech) override {
        std::cout << getName() << " does not speak to the media.  The speech paper is passed to his supervisor.\n";
        getBoss()->stumbleUpon(speech);
    }
};

注意我们如何缩短派生类中的成员函数,由于一个stumbleUpon()将在必要时直接调用另一个成员函数,所以不需要删除类型,因此不需要std::any。此外,没有必要绘制类型索引的地图。然而,尽管如此,我们并没有失去任何程度的灵活性。

票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/283419

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档