2
\$\begingroup\$

The following code has a Handler class that follows the classic Chain of Responsibility pattern. But we don't want to write a new handler class for every new type that we want to handle. I tried making Handler a template class, but it could not work because the class Worker is to derive from Handler, and thus I resorted to using std::any, and now the Handler class handles any type generically. The output of this code shows Worker and its derived types handling objects of the type Dish, Inventory, and SpeechToMedia. The chain of responsibility itself has been allowed to work either down the ranks from the CEO down to the Dishwasher or up the ranks from the Dishwasher up to the CEO, with the chains pre-established from the hiring process which is assumed to have already happened.

#include <iostream>
#include <vector>
#include <any>
#include <typeinfo>
#include <typeindex>
#include <functional>
#include <unordered_map>

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 <typename T> 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 <typename T> 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<Worker*> 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<std::type_index, std::function<void(CEO*)>> 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<std::type_index, std::function<void(CEO*)>> CEO::AnyVisitor::map {
    {std::type_index(typeid(SpeechToMedia*)), [](CEO* ceo) {
            std::cout << ceo->getName() << " speaks to the media.\n";
            std::any_cast<SpeechToMedia*>(ceo->objectHandled)->isDelivered();
            ceo->resetObjectHandled();
        }
    }
};

class Manager : public Worker {
    struct AnyVisitor {
        static const std::unordered_map<std::type_index, std::function<void(Manager*)>> 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<std::type_index, std::function<void(Manager*)>> 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<std::type_index, std::function<void(Supervisor*)>> 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<std::type_index, std::function<void(Supervisor*)>> 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<Inventory*>(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<Dish*>(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<Manager*>(ceo->getManager(a))->getSupervisor(b); }
    Worker* getDishwasher (int a = 0, int b = 0, int c = 0) { return dynamic_cast<Supervisor*>(dynamic_cast<Manager*>(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';
}

Output:

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
\$\endgroup\$

3 Answers 3

2
\$\begingroup\$

The chain of responsibility pattern deals with one request not a bunch of requests. Having to deal with Dishes, Inventory, and Speech is making your code to be cluttered and adding complexity.

The example you have concocted also does not represent real software engineering problems which is further adding to the confusion over the use of this pattern. In a typical implementation of chain of responsibility pattern for you example, should have 3 separate interfaces aka WashDishInterface, MaintainInventoryInterface and a GiveSpeechInterface. Then your Ceo, Manager, Supervisor and Dishwasher can implement the interfaces like so.

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:
};

General Review

  • Use proper spacing to make the code more readable. It is very difficult to know where the Supervior class ends and Supervisor::AnyVisitor::map initialization begins.
  • Use some naming convention to seperate member variables from function local variables. This prevents variable shadowing.
  • delete everything you new. Or better yet use smart pointers. Smart pointers also give you the ability to signify ownership. A class that has a smart pointer member owns the object it is pointing to. A class that has a raw pointer member refers to the object but is not responsible for it's lifetime

Handler class

  • I dont see any need to have objectHandled member variable. Instead you should make the handle function take std::any and get rid of the extra steps of making a copy of the Object and storing it in the Handler.

Worker class

  • The worker class maintains a vector of Workers as subordinates. This is prone to use-after-free undefined behavior. Either have a seperate class manage the company hierarchy or use std::weak_ptr instead. Same with boss memeber variable
  • Having to call setNextHandlerChainAmongSubordinates() method every time you have to handle something can be error prone. Further, it forces a method that can be marked as const to be non-const. When a Dishwasher washes a Dish the only thing that is actually changing is the state of the dish not the dishwasher. Same with setNextHandlerChainAmongBosses()
  • I am not sure why you need a stumbleUpon() method. I can see that in real world a CEO might stumble upon a dish and delegate the task. But in software development objects don't stumble upon things. This breaks Single Responsiblilty Principle now your Worker class is responsible of two thing stumbling upon requests and processing them. This is bad design.

Ceo Class

  • There is no need to override the stumbleUpon() method you are using the chain of responsibility pattern. Just pass the object that you happened to stumble upon to your handle() method and let it figure out how to process it
  • There is no way for the calling code to know workers.size() and thegetManager() function invokes undefined behavior if n => workers.size(). Hence, this function cannot be used without invoking undefined behavior. Use worker.at(n) to do bounds checking. Or better yet change the getManager(int n) to std::vector<Workers*> const getManagers() and let the calling code figure out which manager it wants.
\$\endgroup\$
2
\$\begingroup\$

Manage Your Memory Correctly!

At present, you store C-style pointers everywhere, apparently as weak references, but nothing actually owns any of the workers. You create new worker objects inside the default constructor of Company—and then never delete them! Their memory simply leaks.

Some data structure in the program should own the worker objects, and the other classes should hold weak references to them. These can be bare C-style pointers. Preferably use C-style pointers only as weak references, and objects that do their own memory management for everything else.

In this toy example, the Company object is created by the Ceo constructor and creates every other employee. On this example, one solution that would make sense is for the Company object to hold the data of all its workers, and the workers to hold weak references to the co-workers they might need to pass a request along to. This implies that the Company should construct its Ceo, not the other way around.

Are You Sure You Meant to Delete the Default Constructors?

In your definition of Person, you declare a constructor

Person (const std::string& n) : name(n) { }

This implicitly deletes the default constructor. That makes it impossible to create a default object of Person or any of its derived classes, including Dishwasher, Ceo, etc. Among other things, this means we can’t create an array of them and fill it.

It does, on the other hand, prevent any derived class from existing without a valid name.

Avoid Unnecessary Copies

One example of where this would be appropriate is that you have a constructor Person(const std::string&). However, you actually call this with a temporary string created by string addition. It would be more efficient to move, rather than clone, this temporary string. You should add the constructor Person(std::string&&). This can be both constexpr and noexcept.

Another is that your Person::getName() function returns a deep copy of the name string, which frequently is passed to std::cout. This should return a const std::string& instead. If you actually do nee a copy, assign std::string ceo_name = the_ceo.getName();, and it will make one.

Another place to look out for this is when you are adding a subordinate or setting the boss. Since these hold weak references, it is a logic error to pass in an expiring temporary. You will end up storing a dangling reference! So, you should declare an overload that takes an rvalue reference to a Worker&&, and delete it.

Prefer References to C-Style Pointers

You often pass or return pointers to objects. C-style pointers are useful as weak, non-owning references. Bjarne Stroustrup’s C++ guidelines recommends this is the only way they should be used. It’s continually necessary to check for null pointers, but a (well-formed) reference is valid. References can be overloaded for move semantics, to generate better code. If pointers are always weak references, and other kinds of ownership are consistenly smart pointers the program becomes less ambiguous.

Some of Your Names are a Little Surprising

For example, Inventory::isDone and SpeechToMedia::isDelivered set their condition flag, when many programmers would expect a function with a name like isCondition() to test the condition. That’s named getIsDone, so if you’re following that convention, the setter would be setIsDone.

One common convention is that getters are overloaded line x.done() and setters are overloaded like x.done(true). This is a bit clearer and shorter, but does not allow the setter to take a default value.

You can Streamline This

You don’t seem to need a Handler class. It consists of two member functions, which really do the same thing: handle a job task polymorphically. (It would be convenient to factor out the logic to handle each type of task into its own overload. and use that to dispatch at runtime.)

This one function can then be part of the Worker class interface. It looks here as if any worker could potentially get passed any type of job request, and nothing else possibly could, so I don’t see a reason to have any other kind of interface for request handling than Worker You could also just make stumbleUpon the specific override of handle for a concrete type of Worker and request.

For example:

void Ceo::handle(SpeechToMedia& request)
{
    std::cout << getName() << " expresses regret over the unfortunate situation.\n";
    request.isDelivered();
}

void Dishwasher::handle(Dish& request)
{
    std::cout << getName() << " washes the dish.\n";
    request.getsCleaned();
}

Or Don’t Repeat Yourself; Use Templates

In the example above, the boss and subordinates[0] data members both contain a Worker*. This means that the hand-off to the co-worker’s .handle(request) method needs to call Worker::handle and use dynamic dispatch.

It works, but means every combination of worker type and work type has to be written out manually. You did this anyway, giving every case in the Cartesian product a custom message.

However, in this toy program, most of these were a mad lib like A ________ does not handle ________. ________ asks ________ to take care of it. Furthermore, most of the logic can be summarized in a few simple rules: Requests usually flow downhill; someone at the bottom of the hill (a Dishwasher) has nowhere to send it but up, each specific task is a responsibility of one job title, and a request to speak to the media goes up to the boss, not down.

We could refactor the program to cut down on this code bloat, with templates and concepts. (In other languages, we’d use something like a typeclass, but we can more-or-less fake it in C++ with duck-typing.) However, we can only do this if we can use static dispatch, since a template member function cannot be virtual.

So, first, we want to take boss and subordinates out of Worker and have that be just a Person who can .handle() generically. We add each back to the classes that have them, but with a concrete type. A Manager has a Ceo* boss and a subordinates vector of Supervisor*. A Ceo has no boss, and a Dishwasher has no subordinates.

Next, we give each type of work request the members constexpr bool flows_down, to indicate whether this is stuff that flows uphill or downhill, and static const std::string& describeRequest() to, well, describe the request.

Finally, we declare a concept to indicate that a type is a work request. This makes it more convenient to declare our handle methods as templates.

So, using this approach, Manager::handle gets declared as two templates within the class:

    Supervisor& getNextSubordinate() {
        if (subordinates.size() == 0) {
            throw std::runtime_error("A manager is totally hapless and has nobody to do things for them.");
        }

        return *subordinates.front();
    }

    template<work_request RequestT>
        requires(RequestT::flows_down)
    void handle(RequestT&);

    template<work_request RequestT>
        requires(!RequestT::flows_down)
    void handle(RequestT&);

This then gets implemented as:

template<work_request RequestT>
    requires(RequestT::flows_down)
void Manager::handle(RequestT& request)
{
    auto& schlimazel = getNextSubordinate();

    std::cout << "A manager does not handle "
              << request.describeRequest() << ".  "
              << getName()
              << " asks "
              << schlimazel.getName()
              << " to handle it.\n";

    schlimazel.handle(request);
}

template<work_request RequestT>
    requires(!RequestT::flows_down)
void Manager::handle(RequestT& request)
{
    if (!boss) {
        throw std::runtime_error("A manager is totally hapless and has no boss to tell them what to do.");
    }

    auto& schlemiel = *boss;
    std::cout << "A manager does not handle "
              << request.describeRequest() << ".  "
              << getName() << " asks "
              << schlemiel.getName()
              << " to handle it.\n";
    schlemiel.handle(request);
}

This doesn’t cut out as much boilerplate as it might if we declared handle as a non-member function, because we can’t make the name of the class whose member we’re implementing a template parameter. But it does cut the number of cases from twelve (four types of employee times three types of request) down to nine.

More importantly, when we add a new type of task, we don’t need to add a handler for it in every Worker class. We just need to set a strategy, as a compile-time constant in the class header, and all the existing classes will know where to pass it to. And when we introduce a new layer of management, as we inevitably will, we don’t need to add a case for every type of request that’s ever been written, including those in someone else’s library we don’t know about. We just parameterize the template on the cases that make sense. If someone tries to pass our Worker subtype a request it cannot handle, the compiler will catch the logic error. (Almost. There is a potential infinite loop if you add a new request to my code below.)

You Might not Want std::any

If you can derive every type of job from the same base class—even an empty shell—you can polymorphically determine the type with the typeid operator, and dispatch accordingly. A generic list of tasks would consist of containers of smart pointers to the base class, something like std::dequeue<std::unique_ptr<JobRequest>> pendingRequests;. This lets you write something like pendingRequests.emplace_back(std::make_unique<Dish>()); or pendingRequests.emplace_back(new Dish);.

If that’s not appropriate for your use case, you could also do runtime polymorphism with a std::variant of every type of job request. You state in a comment that a subclass of the handler interface might handle a different set of requests. In that case, have that interface accept a different type of std::variant& representing the requests it can handle, and leave the parent handler accepting the types of requests that it can handle.

If you don’t need runtime polymorphism of different types of job request, you could just implement Worker::handle for each type of request separately (preferably using templates).

Allow const Objects to Work

Currently, .handle() and other functions could be marked const, but are not, meaning that a const object or reference representing a worker can’t handle requests.

Several of these might be constexpr or noexcept as well.

Use Abstract Base Classes

Do you ever need to create a generic Worker that isn’t any subtype of Worker, or is this just an interface? (The same goes for Handler if you keep that, although I recommend folding it into worker, and for JobRequest if you add that superclass.) If not, make it an abstract base class.

An ABC:

  • Must have a virtual destructor. Otherwise, if you try to destroy one through a reference to the base class, the wrong destructor would get called. Usually, this means any data members of the daughter class would not be cleaned up.
  • Should have protected constructors and assignments, by the Rule of Five. Making them protected means that an object of the base class cannot be created, but daughter classes can implicitly call these functions.
  • Should have pure virtual member functions as their interface, which are written like virtual void foo() = 0;. You’re doing this already for .handle(), and the other function can be eliminated.

You can keep many of the data members you have, but some seem inappropriate: for example, does a Ceo have a boss? Does a Dishwasher have a list of subordinates? These would be better represented as concepts, or perhaps subclasses.

Admittedly, writing it this way does force the daughter classes to add boilerplate overriding the entire Worker interface. It does have the compiler stop you from the logic errors of creating a Worker or slicing a derived class.

The Boss/Subordinate Relationships are Impossible to Update

Some parts of the interface, notably addSubordinate and setBoss, attempt to keep the data in the hierarchy consistent, so that a Worker is always a subordinate of their boss, and the boss of their subordinates, if any. But other operations do not keep this data consistent, nor is there any way provided to do so. There is only an interface to add a subordinate, not to remove or replace one.

All the Person/Worker subclasses use the default copy and assign operations. These make shallow copies of the boss and subordinates fields. If any employee in the hierarchy gets moved or deleted, their former boss and subordinates hold a dangling reference. If any employee gets copied, the new object holds references to a boss and employee who’ve never heard of them.

In my test program, I chose to simply disable copy and assign right now, I changed the constructors so that each Worker who is not a Ceo is assigned a boss when created, and emplaced all the employees using these constructors.

Putting it all Together

And finally, the code. First, class declarations:

#include <array>
#include <cassert>
#include <concepts>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>

using namespace std::literals::string_literals;

// Added default constructor for convenience.
class Person {
    std::string name;
public:
    Person() = default;
    constexpr Person (const std::string& n)
     : name(n) { }
    constexpr Person (std::string&& n) noexcept
     : name(std::move(n)) {}
    const std::string& getName() const { return name; }
};

class Dish {
    bool isClean;
public:
    static constexpr bool flows_down = true;

    Dish (bool b = true) : isClean(b) { }

    static const std::string& describeRequest() noexcept
    {
        static const std::string description = "a dirty dish"s;
        return(description);
    }
    void getsCleaned() { isClean = true;  std::cout << "Dirty dish got cleaned.\n"; }
    bool getIsClean() const { return isClean; }
};

class Inventory {
    bool isDone = false;
public:
    static constexpr bool flows_down = true;

    static const std::string& describeRequest() noexcept
    {
        static const std::string description = "some inventory paperwork"s;
        return(description);
    }
    void getsDone() { isDone = true; }
    bool getIsDone() const { return isDone; }
};

class SpeechToMedia {
    bool delivered = false;
public:
    static constexpr bool flows_down = false;
    static const std::string& describeRequest() noexcept
    {
        static const std::string description = "a public statement of regret"s;
        return(description);
    }
    bool showDeliveredStatus() const { return delivered; }
    void isDelivered() { delivered = true; }
};

/* Abstract base class for someone who handles a piece of work.
 */
class Worker : public Person {
    public:

// Always call the destructor of the appropriate subclass.
    virtual ~Worker() = default;
// A pure virtual handle method for generic requests should be declared here.
    protected:

/* This class cannot be instantiated, but its daughter classes can implicitly
 * use its constructors and assignment operators to implement its own..
 */
    Worker() = default;
    constexpr Worker(const std::string& n)
     : Person(n) {}
    constexpr Worker(std::string&& n) noexcept
     : Person(std::move(n)) {}
    Worker(const Worker&) = default;
    Worker(Worker&&) = default;
    Worker& operator=(const Worker&) = default;
    Worker& operator=(Worker&&) = default;
};

template <class RequestT>
concept work_request = std::convertible_to<RequestT, Dish> ||
                       std::convertible_to<RequestT, Inventory> ||
                       std::convertible_to<RequestT, SpeechToMedia>;

// Forward declaration needed by Ceo:
class Manager;

// Simplified:
class Ceo : public Worker {
    std::vector<Manager*> subordinates; // Uses Worker* as a weak pointer..

    public:

    Ceo() = default;
    Ceo(const std::string& name)
     : Worker(name) {}
    Ceo(std::string&& name) noexcept
     : Worker(std::move(name)) {}
    Ceo(const Ceo&) = delete;
    Ceo& operator=(const Ceo&) = delete;
    ~Ceo() override = default;

    Manager& getNextSubordinate() {
        if (subordinates.size() == 0) {
            throw std::runtime_error("The CEO is totally hapless and has nobody to do things for them.");
        }

        return *subordinates.front();
    }

    template<work_request RequestT>
        requires(RequestT::flows_down)
    void handle(RequestT&);

    void handle(SpeechToMedia&);

    void addSubordinate(Manager& w);

    // It is a logic error to add a weak reference to an expiring object!
    void addSubordinate(Manager&&) = delete;
};

// Forward declaration needed by Manager:
class Supervisor;

class Manager : public Worker {
    std::vector<Supervisor*> subordinates;
    Ceo* boss;

    public:

    Manager() = default;
    constexpr Manager(std::string&& name, Ceo& schlemiel) noexcept
     : Worker(std::move(name)) {
        schlemiel.addSubordinate(*this);
    }
    Manager(std::string&&, Ceo&&) = delete;
    constexpr Manager(const std::string& name, Ceo& theirBoss )
     : Manager(std::string(name), theirBoss) {}
    Manager(const std::string&, Ceo&&) = delete;
    Manager(const Manager&) = delete;
    Manager& operator=(const Manager&) = delete;
    ~Manager() override = default;

    Supervisor& getNextSubordinate() {
        if (subordinates.size() == 0) {
            throw std::runtime_error("A manager is totally hapless and has nobody to do things for them.");
        }

        return *subordinates.front();
    }

    template<work_request RequestT>
        requires(RequestT::flows_down)
    void handle(RequestT&);

    template<work_request RequestT>
        requires(!RequestT::flows_down)
    void handle(RequestT&);

    void addSubordinate(Supervisor& w);

    // It is a logic error to add a weak reference to an expiring object!
    void addSubordinate(Supervisor&&) = delete;

    void setBoss(Ceo& w) {
        boss = &w;
    }

    // It is a logic error to add a weak reference to an expiring object!
    void setBoss(Ceo&& w) = delete;

    Ceo& getBoss() const {
        return *boss;
    }
};

// Forward declaration needed by Supervisor:
class Dishwasher;

class Supervisor : public Worker {
    std::vector<Dishwasher*> subordinates;
    Manager* boss;

    public:

    Supervisor() = default;
    constexpr Supervisor(std::string&& name, Manager& schlemiel) noexcept
     : Worker(std::move(name)) {
        schlemiel.addSubordinate(*this);
    }
    Supervisor(std::string&&, Ceo&&) = delete;
    constexpr Supervisor(const std::string& name, Manager& schlemiel )
     : Supervisor(std::string(name), schlemiel) {}
    Supervisor(const std::string&, Manager&&) = delete;
    Supervisor(const Supervisor&) = delete;
    Supervisor& operator=(const Supervisor&) = delete;
    ~Supervisor() override = default;

    Dishwasher& getNextSubordinate() {
        if (subordinates.size() == 0) {
            throw std::runtime_error("The CEO is totally hapless and has nobody to do things for them.");
        }

        return *subordinates.front();
    }

    template<work_request RequestT>
        requires(RequestT::flows_down)
    void handle(RequestT&);

    template<work_request RequestT>
        requires(!RequestT::flows_down)
    void handle(RequestT&);

    void addSubordinate(Dishwasher& w);

    // It is a logic error to add a weak reference to an expiring object!
    void addSubordinate(Dishwasher&&) = delete;

    void setBoss(Manager& w) {
        boss = &w;
    }

    // It is a logic error to add a weak reference to an expiring object!
    void setBoss(Manager&& w) = delete;

    Manager& getBoss() const {
        return *boss;
    }
};

class Dishwasher : public Worker {
    Supervisor* boss;

    public:

    Dishwasher() = default;
    constexpr Dishwasher(std::string&& name, Supervisor& schlemiel) noexcept
     : Worker(std::move(name)) {
        schlemiel.addSubordinate(*this);
    }
    Dishwasher(std::string&&, Supervisor&&) = delete;
    constexpr Dishwasher(const std::string& name, Supervisor& schlemiel )
     : Dishwasher(std::string(name), schlemiel) {}
    Dishwasher(const std::string&, Supervisor&&) = delete;
    Dishwasher(const Dishwasher&) = delete;
    Dishwasher& operator=(const Dishwasher&) = delete;
    ~Dishwasher() override = default;

    template<work_request RequestT>
    void handle(RequestT&);

    void setBoss(Supervisor& w) {
        boss = &w;
    }

    // It is a logic error to add a weak reference to an expiring object!
    void setBoss(Supervisor&& w) = delete;

    Supervisor& getBoss() const {
        return *boss;
    }
};

class Company {
    Ceo ceo;

    std::array<Manager, 2> managers;
    std::array<std::array<Supervisor, 2>,2> supervisors;
    std::array<std::array<std::array<Dishwasher, 2>,2>,2> dishwashers;

    public:

    Company(std::string&& ceo_name);
    Company(const std::string& ceo_name)
     : Company(std::string(ceo_name)) {}
    // 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.
    constexpr const Ceo& getCEO() const noexcept { return ceo; }
    constexpr Ceo& getCEO() noexcept { return ceo; }
    constexpr const Manager& getManager(std::size_t i) const {
        return managers.at(i);
    }
    constexpr Manager& getManager(std::size_t i) {
        return managers.at(i);
    }
    constexpr const Supervisor& getSupervisor(std::size_t i, std::size_t j) const
    {
        return supervisors.at(i).at(j);
    }
    constexpr Supervisor& getSupervisor(std::size_t i, std::size_t j)
    {
        return supervisors.at(i).at(j);
    }
    constexpr const Dishwasher& getDishwasher(std::size_t i, std::size_t j, std::size_t k) const
    {
        return dishwashers.at(i).at(j).at(k);
    }
    constexpr Dishwasher& getDishwasher(std::size_t i, std::size_t j, std::size_t k)
    {
        return dishwashers.at(i).at(j).at(k);
    }
};

Then, definitions outside the class body. (Some of these would not, as written, be able to go in a different .cpp file.)

template<work_request RequestT>
    requires(RequestT::flows_down)
void Ceo::handle(RequestT& request)
{
    auto& schlimazel = getNextSubordinate();

    std::cout << "The CEO does not handle "
              << request.describeRequest() << ".  "
              << getName()
              << " asks "
              << schlimazel.getName()
              << " to handle it.\n";

    schlimazel.handle(request);
}

// Stuff flows downhill, except for:
void Ceo::handle(SpeechToMedia& request)
{
    std::cout << getName() << " expresses regret over the unfortunate situation.\n";
    request.isDelivered();
}

template<work_request RequestT>
    requires(RequestT::flows_down)
void Manager::handle(RequestT& request)
{
    auto& schlimazel = getNextSubordinate();

    std::cout << "A manager does not handle "
              << request.describeRequest() << ".  "
              << getName()
              << " asks "
              << schlimazel.getName()
              << " to handle it.\n";

    schlimazel.handle(request);
}

template<work_request RequestT>
    requires(!RequestT::flows_down)
void Manager::handle(RequestT& request)
{
    if (!boss) {
        throw std::runtime_error("A manager is totally hapless and has no boss to tell them what to do.");
    }

    auto& schlemiel = *boss;
    std::cout << "A manager does not handle "
              << request.describeRequest() << ".  "
              << getName() << " asks "
              << schlemiel.getName()
              << " to handle it.\n";
    schlemiel.handle(request);
}

template<work_request RequestT>
    requires(RequestT::flows_down)
void Supervisor::handle(RequestT& request)
{
    auto& schlimazel = getNextSubordinate();

    std::cout << "A supervisor does not handle "
              << request.describeRequest() << ".  "
              << getName()
              << " asks "
              << schlimazel.getName()
              << " to handle it.\n";

    schlimazel.handle(request);
}

template<work_request RequestT>
    requires(!RequestT::flows_down)
void Supervisor::handle(RequestT& request)
{
    if (!boss) {
        throw std::runtime_error("A supervisor is totally hapless and has no boss to tell them what to do.");
    }

    auto& schlemiel = *boss;
    std::cout << "A supervisor does not handle "
              << request.describeRequest() << ".  "
              << getName() << " asks "
              << schlemiel.getName()
              << " to handle it.\n";
    schlemiel.handle(request);
}

template<>
void Supervisor::handle(Inventory& request)
{
    std::cout << getName() << " does the inventory paperwork.\n";
    request.getsDone();
}

template<work_request RequestT>
void Dishwasher::handle(RequestT& request)
{
    if (!boss) {
        throw std::runtime_error("A dishwasher is left unsupervised.");
    }

    auto& schlemiel = *boss;
    std::cout << getName() << " deigns to allow "
              << schlemiel.getName()
              << " to handle "
              << request.describeRequest() << ".\n";
    schlemiel.handle(request);
}

template<>
void Dishwasher::handle(Dish& request)
{
    std::cout << getName() << " washes the dish.\n";
    request.getsCleaned();
}

void Ceo::addSubordinate(Manager& schlimazel)
{
    subordinates.emplace_back(&schlimazel);
    schlimazel.setBoss(*this);
}

void Manager::addSubordinate(Supervisor& schlimazel)
{
    subordinates.emplace_back(&schlimazel);
    schlimazel.setBoss(*this);
}

void Supervisor::addSubordinate(Dishwasher& schlimazel)
{
    subordinates.emplace_back(&schlimazel);
    schlimazel.setBoss(*this);
}

Company::Company(std::string&& ceo_name)
 : ceo(ceo_name),
   managers{{ Manager("Manager 1", ceo), Manager("Manager 2", ceo) }},
   supervisors{{{{ Supervisor("Supervisor 1/1", managers[0]), Supervisor("Supervisor 1/2", managers[0]) }},
                {{ Supervisor("Supervisor 2/1", managers[1]), Supervisor("Supervisor 2/2", managers[1]) }}}},
   dishwashers{{
    {{
        {{ Dishwasher("Dishwasher 1/1/1", supervisors[0][0]), Dishwasher("Dishwasher 1/1/2", supervisors[0][1]) }},
        {{ Dishwasher("Dishwasher 1/2/1", supervisors[0][1]), Dishwasher("Dishwasher 1/2/2", supervisors[0][1]) }}
    }},
    {{
        {{ Dishwasher("Dishwasher 2/1/1", supervisors[1][0]), Dishwasher("Dishwasher 2/1/2", supervisors[1][0]) }},
        {{ Dishwasher("Dishwasher 2/2/1", supervisors[1][1]), Dishwasher("Dishwasher 2/2/2", supervisors[1][1]) }}
    }}
  }}
{}

And finally, a test driver based on yours.

nt main() {
    Company mister_plow("Homer J. Simpson");
    auto& ceo = mister_plow.getCEO();
    Dish dirtyDish(false), secondDirtyDish(false);
    ceo.handle(dirtyDish);
    std::cout << "Dish is clean: " << std::boolalpha << dirtyDish.getIsClean() << "\n\n";
    mister_plow.getManager(1).handle(secondDirtyDish);
    std::cout << "Second dish is clean: " << std::boolalpha << secondDirtyDish.getIsClean() << "\n\n";
    
    Inventory inventory, secondInventory;
    ceo.handle(inventory);
    std::cout << "Inventory is done: " << std::boolalpha << inventory.getIsDone() << "\n\n";
    mister_plow.getDishwasher(1,0,1).handle(secondInventory);
    std::cout << "Second inventory is done: " << std::boolalpha << secondInventory.getIsDone() << "\n\n";
    
    SpeechToMedia speech;
    mister_plow.getDishwasher(1,0,1).handle(speech);
    std::cout << "Speech is delivered:  " << std::boolalpha << speech.showDeliveredStatus() << '\n';
    return EXIT_SUCCESS;
}

You can try it on Godbolt.

\$\endgroup\$
1
\$\begingroup\$

There are several issues with your approach. I think this stems from the fact that saw how the chain of responsibility was implemented in some example code, and noticed that it sets the next handler using an appendNext() or SetNext() member function of the base class (like in the Wikipedia article), and then wanted to use that to change the order in which the chain is traversed while handling the object. However, those member functions were only meant to be called once when constructing a static chain of responsibility. If you want it to be dynamic, I would not have a setNext() in Handler at all. Instead, you could write the derived classes like:

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);
    }
};

Note how we shortened the member functions in the derived classes, and since one stumbleUpon() will directly call another one if necessary, no type erasure is necessary, so no need for std::any. Also no map of type indices is necessary. Yet despite this, we have not lost any degree of flexibility.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.