2
\$\begingroup\$

Each animal type will call reactToOwner() and exhibit its own behaviour. So simply define a reactToOwner() override for each animal type, right? But in this case reactToOwner() has many common parts for each animal type, e.g. lookAtOwner(), doNothing(), goAboutOwnBusiness(). So simply define hook methods between them and define their own overrides in each animal type, right? Yes, but some such hook methods will not apply to some animal types (e.g. a Cow will not call jumpAndDown()). So the best I could think of is to use templates and check the template type during compile time with if constexpr, which does not cost any inefficiency, though the dynamic casting does. When adding new lines to Animal::doReactToOwner() in the future, one can check what type needs what, and any new lines that will be used by all Animal types can be added directly, whereas conventional doReactToOwner() overrides for each animal type (Strategy Pattern) may easily miss such lines that are to be shared with all animal types.

#include <iostream>
#include <type_traits>

struct Animal {
    template <typename T> void doReactToOwner() const;
    virtual void reactToOwner() const = 0;
    void lookAtOwner() const { std::cout << "Looks at owner.\n"; }
    void turnQuiet() const { std::cout << "Turns quiet.\n"; }
    void doNothing() const { }
    void goAboutOwnBusiness() const { std::cout << "Does its own thing.\n"; }
};

template <typename T>
struct AnimalCRTP : Animal {
    virtual void reactToOwner() const override { doReactToOwner<T>(); }
    void jumpsUpAndDown() const { std::cout << "Jumps up and down.\n"; }
};

struct Dog : AnimalCRTP<Dog> {
    void barks() const { std::cout << "Barks.\n"; }
};

struct Cat : AnimalCRTP<Cat> {
    void meows() const { std::cout << "Meows.\n"; }
};

struct Cow : AnimalCRTP<Cow> {
    void moos() const { std::cout << "Moos.\n"; }
};

template <typename T>
void Animal::doReactToOwner() const {
    lookAtOwner();
    if constexpr (std::is_same_v<T, Dog>)
        dynamic_cast<const Dog*>(this)->barks();
    if constexpr (std::is_same_v<T, Dog> || std::is_same_v<T, Cat>)
        dynamic_cast<const T*>(this)->jumpsUpAndDown();
    if constexpr (std::is_same_v<T, Cat> || std::is_same_v<T, Cow>)
        turnQuiet();
    doNothing();
    if constexpr (std::is_same_v<T, Cat>)
        dynamic_cast<const Cat*>(this)->meows();
    else if constexpr (std::is_same_v<T, Cow>)
        dynamic_cast<const Cow*>(this)->moos();
    goAboutOwnBusiness();
}

int main() {
    std::cout << "Dog reacts:\n";
    Dog dog;
    dog.reactToOwner();
    std::cout << "\nCat reacts:\n";
    Cat cat;
    cat.reactToOwner();
    std::cout << "\nCow reacts:\n";
    Cow cow;
    cow.reactToOwner();
}

Output :

Dog reacts:
Looks at owner.
Barks.
Jumps up and down.
Does its own thing.

Cat reacts:
Looks at owner.
Jumps up and down.
Turns quiet.
Meows.
Does its own thing.

Cow reacts:
Looks at owner.
Turns quiet.
Moos.
Does its own thing.
\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

Captain Hatteras has a nice answer, I just want to present another way of doing it that does not use CRTP at all, but does address your remark that:

[...] conventional doReactToOwner() overrides for each animal type (Strategy Pattern) may easily miss such lines that are to be shared with all animal types.

The solution to this would be to have a non-virtual reactToOwner() in the base class that has all the behavior that is shared with all animals, but which calls further virtual functions that do only the animal-specific things. For example:

struct Animal {
    void reactToOwner() const;
protected:
    virtual void initialReactToOwner() const = 0;
    virtual void finalReactToOwner() const = 0;
    void lookAtOwner() const { std::cout << "Looks at owner.\n"; }
    void jumpsUpAndDown() const { std::cout << "Jumps up and down.\n"; }
    void turnQuiet() const { std::cout << "Turns quiet.\n"; }
    void doNothing() const { }
    void goAboutOwnBusiness() const { std::cout << "Does its own thing.\n"; }
};

struct Cat: public Animal {
private:
    void initialReactToOwner() const final;
    void finalReactToOwner() const final;
    void meows() const { std::cout << "Meows.\n"; }
};

void Animal::reactToOwner() const {
    lookAtOwner();
    initialReactToOwner();
    doNothing();
    finalReactToOwner();
    goAboutOwnBusiness();
}

void Cat::initialReactToOwner() const {
    jumpsUpAndDown();
    turnQuiet();
}

void Cat::finalReactToOwner() const {
    meows();
}

P.S.: cows do jump.

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

Small things:

  • When dynamic_cast is used on a pointer and fails, it returns nullptr. In this case, it can't fail because you check the type beforehand, so use static_cast (if you keep your current design).
  • Animal's destructor should be virtual.

In this example, you don't really need polymorphism. You could just use CRTP without loss.

Instead of having the AnimalCRTP's doReactToOwner, you could put common functionality into structs and implement doReactToOwner in every animal. This would allow users to make their own animals.

Here is one way of implementing this:

#include <iostream>
#include <type_traits>

template <typename T>
struct AnimalCRTP {
    void reactToOwner() const {
        static_cast<const T*>(this)->reactToOwner();
    }
};

template<typename T>
struct TameAnimal : public AnimalCRTP<T>{
    void goAboutOwnBusiness() const { std::cout << "Does its own thing.\n"; }
    void lookAtOwner() const { std::cout << "Looks at owner.\n"; }
};

// Might want to shorten the name... 

struct DogCatCommonFunctionality{
    void jumpUpAndDown() const{ std::cout << "Jumps up and down.\n"; }
};

struct CowCatCommonFunctionality{
    void turnQuiet() const { std::cout << "Turns quiet.\n"; }
};

struct Dog : public TameAnimal<Dog>,
             public DogCatCommonFunctionality
{
    void barks() const { std::cout << "Barks.\n"; }
    void reactToOwner() const{
        lookAtOwner();
        barks();
        jumpUpAndDown();
        goAboutOwnBusiness();
    }
};

struct Cat : public TameAnimal<Cat>, 
             public DogCatCommonFunctionality,
             public CowCatCommonFunctionality
{
    void meows() const { std::cout << "Meows.\n"; }
    void reactToOwner() const{
        lookAtOwner();
        jumpUpAndDown();
        turnQuiet();
        meows();
        goAboutOwnBusiness();
    }
};

struct Cow : public TameAnimal<Cow>, 
             public CowCatCommonFunctionality{
    void moos() const { std::cout << "Moos.\n"; }
    void reactToOwner() const{
        lookAtOwner();
        turnQuiet();
        moos();
        goAboutOwnBusiness();
    }
};
\$\endgroup\$

Your Answer

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

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