Search code examples
c++classoopcircular-dependency

Removing circular dependencies between classes in C++


Suppose we are making a two player card game, and we have classes called Game, Player, and Card. Game contains a pointer to the two players & provides an interface for the players. Player consists of the health of the player & their magic along with a vector of cards which is their hand. Card is an abstract class. Each Card costs magic to play, and can be played.

The problem is that each Card, when played, can alter the game state in any number of ways. For example, we could have a Card which doubles a player's health, a Card which poisons the enemy player for two turns, a Card which destroys all minions on the board, a Card which creates a copy of each minion already on the board, etc. The possibilities are really endless. The only solution I can see is having a pointer to Game inside each Card, but this seems rather inelegant.

Are there any other solutions?


Solution

  • Your basic idea is correct but you should enhance it further to keep a high level of abstraction and encapsulation.

    So in your example let's say you have a Card class which is an instance of an existing class.

    The first thing you can do is to split the effect of the card from the card itself, for example:

    class CardEffect {
     // TODO ...
    };
    
    class Card {
    private:
      const CardEffect* effect;
    };
    

    So now the card itself doesn't need to know anything about the game. But let's get deeper into this: a CardEffect doesn't need to know every detail of Game class, what it needs to be able to do is to apply effects to the game. This can be done by providing a separate interface to the effect which only exposes what's needed to apply the effect, so something like this.

    class GameInterface {
    public:
      virtual const std::vector<Player*>& getPlayers() = 0;
    
      virtual damagePlayer(Player* player, int amount) = 0;
      virtual applyPeriodicDamage(Player* player, int turns, int amount) = 0
    
      ..
    };
    
    class CardEffect {
      virtual applyEffect(GameInterface* interface) = 0;
    };
    
    class Card {
    private:
      const CardEffect* effect;
    };
    

    Now this is just an example, there's no definitive solution to your problem since each specific scenario is different and has different requirements, it's just to give you a basic idea in how you could try to keep code elegant and encapsulated while keeping it enough descriptive to be easy to manage.