Template method pattern mess

181 Views Asked by At

Here the hook functions themselves need to check the typeid of a data member that also belongs to hierarchy of classes. So I define a template method for that hierarchy of classes. This is the mess I run into:

void Person::leave() {
    // code
    hook();  // private virtual 
    // code
}

void Girl::hook() {  // Girl is a derived class of Person, with data member location 
                 // of type Location which itself has a derived class House
    // code
    location.locationHook(this);// what happens here depends on what kind of location she is in
    // code
}

void Location::locationHook(Person* person) {
    // Oh oh!  This depends on what class person is
}

void House::locationHook(Person* person) {
    // Oh oh!  This depends on what class person is

}

So for this situation, I have to resort to my original method of using typeid(*person) and dynamic_cast and if-statements for each type of Person derived class to define the virtual locationHook's, right?

2

There are 2 best solutions below

0
On

This is the solution I came up with. I tested that it works, but I don't know if it gets any approval or not (or if it will always work):

void Person::leave() {
    // code
    hook();
}

void Person::hook() {
    // code
    location.locationHook (this);
}

void Girl::hook() {
    // code 
    location.locationHook (this);
}

void Location::locationHook (Person* person) {
    // code
    person->removeFromLocation();
}

void House::locationHook (Person* person) {
    // code 
    person->removeFromHouse();
}

// removeFromLocation() and removeFromHouse() are also virtual functions of Person
0
On

Here's an alternate solution using double dispatch:

#include <iostream>
#include <list>

struct Person;  struct Girl;  struct Guy;

struct Location {
    virtual void hook (Person*) = 0;
    virtual void hook (Girl*) = 0;
    virtual void hook (Guy*) = 0;
};

struct House : Location {
    virtual void hook (Person*) override;
    virtual void hook (Girl*) override {
        std::cout << "Girl left the house.\n";
    }
    virtual void hook (Guy*) override {
        std::cout << "Guy left the house.\n";
    }
};

struct Person {
    Location* location = new House;
    void leave() {
        // ...
        hook();
        // ...
    }
    virtual void invoke_hook (Location*) = 0;
private:
    virtual void hook() = 0;
};

struct Girl : Person {
    virtual void hook() override { 
        std::cout << "Girl will leave the house.\n";
        location->hook(this);
        // ...
    }
    virtual void invoke_hook (Location* location) override { location->hook(this); }  // Double dispatch
};

struct Guy : Person {
    virtual void hook() override { 
        std::cout << "Guy will leave the house.\n";
        location->hook(this);
        // ...
    }
    virtual void invoke_hook (Location* location) override { location->hook(this); }  // Double dispatch
};

void House::hook (Person* person) {
    person->invoke_hook(this);  // Double dispatch
}

int main() {
    std::list<Person*> people = {new Girl, new Guy};
    for (Person* p : people)
        p->leave();  // Simple overloads will do the job here.

    House* house = new House;
    for (Person* p : people)
        house->hook(p);  // Double dispatch used.
}

Output:

Girl will leave the house.
Girl left the house.
Guy will leave the house.
Guy left the house.
Girl left the house.
Guy left the house.