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.