When a Wizard
casts the Monster Summoning spell, all of the summoned monsters shall follow his command. Should the Wizard interact with his summoned monsters directly (what are the potential pitfalls in this case?) or indirectly through a mediator?
In my code below, I'm using a mediator, but since such a specialized mediator (used only for this spell) do not pertain to all living beings (most living beings cannot even cast spells), I've placed the mediator data member only within a specific State class that is only created when the spell is cast. Here I have the mediator registering the summoned monsters when the spell is cast, dispatching the commands from the wizard to the summoned monsters, unregistering a summoned monster when it dies, and destroying all the summoned monsters in the event that the wizard himself dies (or loses his concentration).
Note: all classes are structs here for simplicity.
#include <iostream>
#include <list>
#include <string>
struct Mediator {
struct LivingBeing* being;
Mediator (LivingBeing* b) : being(b) {}
};
struct MonsterSummoningMediator : Mediator {
std::list<struct SummonedMonster*> summonedMonsters;
using Mediator::Mediator;
~MonsterSummoningMediator() {destroySummonedMonsters();}
void registerSummonedMonster (SummonedMonster* m) {summonedMonsters.emplace_back(m);}
void unregisterSummonedMonster (SummonedMonster* m) {summonedMonsters.remove(m);}
inline void distributeAttackCommand();
inline void distributeShieldMeCommand();
inline void destroySummonedMonsters();
};
struct State {
LivingBeing* being;
State (LivingBeing* b) : being(b) {}
virtual ~State() = default;
};
struct MonsterSummonerState : State {
MonsterSummoningMediator* mediator;
MonsterSummonerState (LivingBeing* being) :
State(being), mediator(new MonsterSummoningMediator(being)) {}
~MonsterSummonerState() {delete mediator;}
void addSummonedMonster(SummonedMonster* m) {mediator->registerSummonedMonster(m);}
};
struct LivingBeing {
std::string name;
State* state;
LivingBeing() = default;
LivingBeing (const std::string& n) : name(n) {}
virtual ~LivingBeing() = default;
virtual void dies() {std::cout << "\n" << name << " dies.\n";}
};
struct Monster : LivingBeing {
using LivingBeing::LivingBeing;
virtual ~Monster() {std::cout << name << " destroyed.\n";}
};
struct SummonedMonster {
Monster* monster;
LivingBeing* summoner;
SummonedMonster (Monster* m, LivingBeing* s) : monster(m), summoner(s) {}
~SummonedMonster() {delete monster;}
void receiveAttackCommand() {
std::cout << monster->name << " will follow " << summoner->name << "'s order to attack.\n";
}
void receiveShieldMeCommand() {
std::cout << monster->name << " will follow " << summoner->name << "'s order to shield him.\n";
}
};
struct Wizard : LivingBeing {
using LivingBeing::LivingBeing;
void castsMonsterSummoning() {
state = new MonsterSummonerState(this);
MonsterSummonerState* monsterSummonerState = dynamic_cast<MonsterSummonerState*>(state);
for (int i = 0; i < 5; i++) {
Monster* monster = new Monster("Summoned Goblin");
monsterSummonerState->addSummonedMonster(new SummonedMonster(monster, this));
}
std::cout << "Wizard::castsMonsterSummoning() called by " << name << ".\n";
}
// The MonsterSummoningMediator passes the order to all the summoned monsters.
// There is no direct interaction between the summoner and the summoned monsters.
void orderSummonedMonstersToAttack() {
dynamic_cast<MonsterSummonerState*>(state)->mediator->distributeAttackCommand();
}
void orderSummonedMonstersToShieldMe() {
dynamic_cast<MonsterSummonerState*>(state)->mediator->distributeShieldMeCommand();
}
virtual void dies() override {
LivingBeing::dies();
if (dynamic_cast<MonsterSummonerState*>(state)) // if (isAMonsterSummoner())
endMonsterSummoningSpell();
}
void endMonsterSummoningSpell() {
std::cout << "\n" << name << "'s Monster Summoning spell has ended.\n";
delete state;
state = new State(this);
}
};
inline void MonsterSummoningMediator::distributeAttackCommand() {
for (SummonedMonster* x : summonedMonsters)
x->receiveAttackCommand();
}
inline void MonsterSummoningMediator::distributeShieldMeCommand() {
for (SummonedMonster* x : summonedMonsters)
x->receiveShieldMeCommand();
}
inline void MonsterSummoningMediator::destroySummonedMonsters() {
for (SummonedMonster* x : summonedMonsters)
delete x;
summonedMonsters.clear();
}
int main() {
Wizard wizard("Merlin");
wizard.castsMonsterSummoning();
wizard.orderSummonedMonstersToAttack();
wizard.orderSummonedMonstersToShieldMe();
wizard.dies();
}
Output:
Wizard::castsMonsterSummoning() called by Merlin. Summoned Goblin will follow Merlin's order to attack. Summoned Goblin will follow Merlin's order to attack. Summoned Goblin will follow Merlin's order to attack. Summoned Goblin will follow Merlin's order to attack. Summoned Goblin will follow Merlin's order to attack. Summoned Goblin will follow Merlin's order to shield him. Summoned Goblin will follow Merlin's order to shield him. Summoned Goblin will follow Merlin's order to shield him. Summoned Goblin will follow Merlin's order to shield him. Summoned Goblin will follow Merlin's order to shield him. Merlin dies. Merlin's Monster Summoning spell has ended. Summoned Goblin destroyed. Summoned Goblin destroyed. Summoned Goblin destroyed. Summoned Goblin destroyed. Summoned Goblin destroyed.
So far, the wizard and his summoned monsters are unaware of each other in the code. But direct interaction will occur between them and the wizard's enemies when they fight each other (this is shown below). Yet the summoned monsters are not really monsters--they are temporary illusions that can cause damage, so I'm confused about what the design should really be.
Should the proxy class SummonedMonster
even be used at all? That's another issue I have. The reason for SummonedMonster
is that it holds the data member SummonedMonster::summoner
, which should not be in Monster
(since not all monsters are summoned). But SummonedMonster::name
has no value. Only SummonedMonster::monster->name
has the desired value. Though I could define SummonedMonster::getName() const {return monster->name;}
, I will have to do this for all data members of Monster
. Should I instead define a State derived class SummonedMonsterState
instead?
Here is an extended code illustrating the problems I've mentioned:
#include <iostream>
#include <list>
#include <string>
struct LivingBeing;
std::list<LivingBeing*> allBeingsPresent;
struct Mediator {
LivingBeing* being;
Mediator (LivingBeing* b) : being(b) {}
};
struct MonsterSummoningMediator : Mediator {
std::list<struct SummonedMonster*> summonedMonsters;
using Mediator::Mediator;
~MonsterSummoningMediator() {destroySummonedMonsters();}
void registerSummonedMonster (SummonedMonster* monster) {summonedMonsters.emplace_back(monster);}
void unregisterSummonedMonster (SummonedMonster* monster) {summonedMonsters.remove(monster);}
inline void distributeAttackCommand();
inline void distributeShieldMeCommand();
inline void destroySummonedMonsters();
};
struct State {
LivingBeing* being;
State (LivingBeing* b) : being(b) {}
virtual ~State() = default;
};
struct MonsterSummonerState : State {
MonsterSummoningMediator* mediator;
MonsterSummonerState (LivingBeing* being) :
State(being), mediator(new MonsterSummoningMediator(being)) {}
~MonsterSummonerState() {delete mediator;}
};
struct LivingBeing {
std::string name;
State* state;
LivingBeing() = default;
LivingBeing (const std::string& n) : name(n) {allBeingsPresent.emplace_back(this);}
virtual ~LivingBeing() = default;
virtual void dies() {std::cout << "\n" << name << " dies.\n";}
void attacks (LivingBeing* target) {
std::cout << "\n" << name << " attacks " << target->name << ".\n";
target->dies();
}
};
struct Monster : LivingBeing {
using LivingBeing::LivingBeing;
virtual ~Monster() {std::cout << name << " destroyed.\n";}
};
struct SummonedMonster : Monster {
Monster* monster; // Proxy Pattern
LivingBeing* summoner;
SummonedMonster (Monster* m, LivingBeing* s) : monster(m), summoner(s) {
allBeingsPresent.emplace_back(this);
}
~SummonedMonster() {delete monster;}
void receiveAttackCommand() {std::cout << monster->name << " will follow " << summoner->name << "'s order to attack.\n";}
void receiveShieldMeCommand() {std::cout << monster->name << " will follow " << summoner->name << "'s order to shield him.\n";}
virtual inline void dies() override;
};
struct CharacterClass : LivingBeing {
using LivingBeing::LivingBeing;
};
struct Fighter : CharacterClass {
using CharacterClass::CharacterClass;
};
struct Wizard : CharacterClass {
using CharacterClass::CharacterClass;
void castsMonsterSummoning() {
state = new MonsterSummonerState(this);
MonsterSummoningMediator* monsterSummoningMediator = getMonsterSummoningMediator();
for (int i = 0; i < 5; i++) {
Monster* monster = new Monster("Summoned Goblin");
monsterSummoningMediator->registerSummonedMonster(new SummonedMonster(monster, this));
allBeingsPresent.remove(monster); // Since only the new SummonedMonster* is to be inserted into allBeingsPresent.
}
}
MonsterSummoningMediator* getMonsterSummoningMediator() const {
return dynamic_cast<MonsterSummonerState*>(state)->mediator;
}
void orderSummonedMonstersToAttack() {
getMonsterSummoningMediator()->distributeAttackCommand();
}
void orderSummonedMonstersToShieldMe() {
getMonsterSummoningMediator()->distributeShieldMeCommand();
}
virtual void dies() override {
LivingBeing::dies();
if (dynamic_cast<MonsterSummonerState*>(state)) // if (isAMonsterSummoner())
endMonsterSummoningSpell();
}
void endMonsterSummoningSpell() {
std::cout << "\n" << name << "'s Monster Summoning spell has ended.\n";
delete state;
state = new State(this);
}
};
inline void SummonedMonster::dies() {
std::cout << "\n" << monster->name << " dies.\n"; // Mediator now unregisters 'this'.
dynamic_cast<Wizard*>(summoner)->getMonsterSummoningMediator()->unregisterSummonedMonster(this);
}
inline void MonsterSummoningMediator::distributeAttackCommand() {
for (SummonedMonster* x : summonedMonsters)
x->receiveAttackCommand();
}
inline void MonsterSummoningMediator::distributeShieldMeCommand() {
for (SummonedMonster* x : summonedMonsters)
x->receiveShieldMeCommand();
}
inline void MonsterSummoningMediator::destroySummonedMonsters() {
for (SummonedMonster* x : summonedMonsters)
delete x;
summonedMonsters.clear();
}
int main() {
Wizard wizard("Merlin");
Fighter fighter("Borg");
wizard.castsMonsterSummoning();
for (LivingBeing* x : allBeingsPresent) std::cout << x->name << " is present.\n";
wizard.orderSummonedMonstersToAttack();
wizard.orderSummonedMonstersToShieldMe();
fighter.attacks(allBeingsPresent.back()); // The summoned monster dies.
wizard.dies();
}
Output:
Merlin is present. Borg is present. is present. // name value of the summoned monster missing is present. // and thus all other data member values missing. is present. is present. is present. Summoned Goblin will follow Merlin's order to attack. Summoned Goblin will follow Merlin's order to attack. Summoned Goblin will follow Merlin's order to attack. Summoned Goblin will follow Merlin's order to attack. Summoned Goblin will follow Merlin's order to attack. Summoned Goblin will follow Merlin's order to shield him. Summoned Goblin will follow Merlin's order to shield him. Summoned Goblin will follow Merlin's order to shield him. Summoned Goblin will follow Merlin's order to shield him. Summoned Goblin will follow Merlin's order to shield him. Borg attacks . // name value of the summoned monster missing Summoned Goblin dies. Merlin dies. Merlin's Monster Summoning spell has ended. Summoned Goblin destroyed. destroyed. Summoned Goblin destroyed. destroyed. Summoned Goblin destroyed. destroyed. Summoned Goblin destroyed. destroyed.