HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Use of Mediator Pattern or Proxy Pattern for a game

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
proxygameformediatorusepattern

Problem

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
#include
#include

struct Mediator {
struct LivingBeing* being;
Mediator (LivingBeing* b) : being(b) {}
};

struct MonsterSummoningMediator : Mediator {
std::list 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 LivingBei

Solution

The flaws of using the proxy pattern demonstrated above will be removed if I use the adapter pattern instead:

struct LivingBeing {
    struct SummonedTag {};
    std::string name;
    State* state;
    LivingBeing() = default;
    LivingBeing (const std::string& n) : name(n) {allBeingsPresent.emplace_back(this);}
    LivingBeing (const std::string& n, SummonedTag) : name(n) {}  // Do NOT call 'allBeingsPresent.emplace_back(this);' else there will be duplication.
    // ...
};

struct Monster : LivingBeing {
    using LivingBeing::LivingBeing;
    // ...
};

struct Goblin : Monster { using Monster::Monster; };

struct SummonedMonster : Monster {
    LivingBeing* summoner;
    SummonedMonster (LivingBeing* s, const std::string& name) : Monster(name), summoner(s) {}
    // ...
}

template 
struct Summoned : SummonedMonster, T {  // *** Adapter Pattern
    Summoned (LivingBeing* summoner, const std::string& name) : SummonedMonster(summoner, name),
        T(name, SummonedTag{}) {}
};

template 
void Wizard::castsMonsterSummoning() {
        state = new MonsterSummonerState(this);
        MonsterSummoningMediator* monsterSummoningMediator = getMonsterSummoningMediator();
        for (int i = 0; i registerSummonedMonster(new Summoned(this, "Summoned Monster"));
    }
}


The resulting output does not show the problems in the question's output:

Wizard::castsMonsterSummoning() called by Merlin.
monsterSummoningMediator->summonedMonsters.size() = 5
Merlin is present.
Borg is present.
Summoned Monster is present.
Summoned Monster is present.
Summoned Monster is present.
Summoned Monster is present.
Summoned Monster is present.
Summoned Monster will follow Merlin's order to attack.
Summoned Monster will follow Merlin's order to attack.
Summoned Monster will follow Merlin's order to attack.
Summoned Monster will follow Merlin's order to attack.
Summoned Monster will follow Merlin's order to attack.
Summoned Monster will follow Merlin's order to shield him.
Summoned Monster will follow Merlin's order to shield him.
Summoned Monster will follow Merlin's order to shield him.
Summoned Monster will follow Merlin's order to shield him.
Summoned Monster will follow Merlin's order to shield him.

Borg attacks Summoned Monster.

Summoned Monster dies.

Merlin dies.

Merlin's Monster Summoning spell has ended.
MonsterSummoningMediator destroyed.
Summoned Monster destroyed.
Summoned Monster destroyed.
Summoned Monster destroyed.
Summoned Monster destroyed.
Summoned Monster destroyed. // Hmmm... duplicate destructor call (seems harmless though).
Summoned Monster destroyed.
Summoned Monster destroyed.
Summoned Monster destroyed.


I still have to investigate if this works in more complex constructions though.

Update: I've experimented this new design with more complex constructions and its seems to be working fine. Here is my latest design, in case you wanted to see the whole thing:

`#include
#include
#include

struct LivingBeing;

std::list allBeingsPresent;

struct Mediator {
LivingBeing* being;
Mediator (LivingBeing* b) : being(b) {}
};

struct MonsterSummoningMediator : Mediator {
std::list summonedMonsters;
using Mediator::Mediator;
~MonsterSummoningMediator() {std::cout name losesHitPoints(20); target->dies();}
};

struct Monster : LivingBeing {
using LivingBeing::LivingBeing;
virtual ~Monster() {std::cout spellsKnown;
std::string specialty;
int numWands;
template
GoblinWizard (const LivingBeing::Data& data, const std::list& spells, const std::string s, int num, Args&&... args) :
Goblin(data, std::forward(args)...), spellsKnown(spells), specialty(s), numWands(num) {}
};

struct Lich : Monster {
struct Data {
std::list spellsKnown;
int undeadLevel;
std::string deity;
};
Data lichData;
template
Lich (const LivingBeing::Data& data, const Data& lData, Args&&... args) : Monster(data, std::forward(args)...), lichData(lData) {}
};

struct SummonedMonster : Monster {
LivingBeing* summoner;
SummonedMonster (LivingBeing* s, const LivingBeing::Data& data) : Monster(data, SummonedTag{}), summoner(s) {}
void receiveAttackCommand() {std::cout name name
struct Summoned : SummonedMonster, T { // Adapter Pattern
template
Summoned (LivingBeing* summoner, const LivingBeing::Data& data, Args&&... args) : SummonedMonster(summoner, data), T(data, std::forward(args)..., SummonedTag{}) {
allBeingsPresent.emplace_back(dynamic_cast(this)); // This is the pointer that we want to insert into allBeingsPresent.
}
};

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 = getMonsterSummon

Code Snippets

struct LivingBeing {
    struct SummonedTag {};
    std::string name;
    State* state;
    LivingBeing() = default;
    LivingBeing (const std::string& n) : name(n) {allBeingsPresent.emplace_back(this);}
    LivingBeing (const std::string& n, SummonedTag) : name(n) {}  // Do NOT call 'allBeingsPresent.emplace_back(this);' else there will be duplication.
    // ...
};

struct Monster : LivingBeing {
    using LivingBeing::LivingBeing;
    // ...
};

struct Goblin : Monster { using Monster::Monster; };

struct SummonedMonster : Monster {
    LivingBeing* summoner;
    SummonedMonster (LivingBeing* s, const std::string& name) : Monster(name), summoner(s) {}
    // ...
}

template <typename T>
struct Summoned : SummonedMonster, T {  // *** Adapter Pattern
    Summoned (LivingBeing* summoner, const std::string& name) : SummonedMonster(summoner, name),
        T(name, SummonedTag{}) {}
};

template <typename T>
void Wizard::castsMonsterSummoning() {
        state = new MonsterSummonerState(this);
        MonsterSummoningMediator* monsterSummoningMediator = getMonsterSummoningMediator();
        for (int i = 0; i < 5; i++)
            monsterSummoningMediator->registerSummonedMonster(new Summoned<T>(this, "Summoned Monster"));
    }
}

Context

StackExchange Code Review Q#73567, answer score: 3

Revisions (0)

No revisions yet.