2

I'm doing a class diagram for a game project, but I'm stuck on a coupling problem.

Context

The projet is a turn-based game. Two users are playing. They've got units on a map. They play until 1 player has all his units dead or if a predefined number of turns are reached. At each round, each player will review his units one by one:

  • he can move an unit until the unit hasn't got any movement point left
  • he can skip the unit's turn

Modeling

Class diagram

Classes irrelevant to the questions have been cut

The Game class holds all the relevant objects : the players, the units, the current turn (which player(s) have already played, who's next, ...). The Turn object holds a PlayerTurn object, which keeps tracks of which units have already been played, which units are left to play, ...

Problem

-> Let's suppose we're creating the GetNextPlayableUnit method (in class PlayerTurn).

We'll have to iterate through the current player's units and find one that is not in the "PlayedUnits" list. Therefore, we'll have to call Game.Units.GetUnits with the parameter Game.Turn.CurrentPlayer (Game refers to the current Game's instance).

-> Let's suppose we're creating the MoveTo method (in class Unit).

If the Unit hasn't got any movement point left, we'll have to call Game.Turn.PlayerTurn.Finish which will either select the next playable unit or finish the current player's turn if he already played all of his units this turn.

-> ... There are a lot of other scenarios where we'll need to access other classes' method. E.g., a "Kill" method on Unit will have to check if the current player has still got at least 1 unit alive, and it will have to call a Finish method on Game otherwise.

Question

I think the coupling can't be avoided (correct me if I'm wrong though!). I've thought about two possible ways of dealing with coupling:

Solution 1

When constructing instances of these classes, I could pass the current Game's instance. After all, it makes sense: Unit instances, Turn instances, ... all belong to a game. Then, I could implement the methods GetNextPlayableUnit, MoveTo, Killed, ... easily by using this instance. Somehow, I don't feel comfortable sharing the current game's instance between all the model classes, even though I read here and there that it was the principle of dependency injection.

Solution 2

  1. Analyse the method dependencies: e.g., Kill method depends on Game.Units.Unit.Player (to retrieve the Player owning the Unit), on Game.Units.GetUnits (to find all other Units owned by the player), on Game.End to end the game if unit count of the player has reached 0.
  2. Find the dependencies common root: in the example above, the common root between Game.Units.Unit, Game.Units and Game is Game. In another example, if we had the dependencies Game.Units.Unit & Game.Units.SomethingElse, the common root would be Game.Units.
  3. Implement the method in this common root class. We're guaranteed not to need to access parent classes (in the association hierarchy).

Risks:

  • lots of methods will have the Game class for common root => lots of methods in Game class => lots of code in Game class.
  • methods won't belong to their rightful classes. Consider the method "KillUnit": you'd expect to find it in the Unit class directly, not in the Game class. Same goes for "MoveTo", "Finish", ...

Is there any other solution you can think of? What would be the best approach in this particular case?

2

You ask about other solutions. These might be reflections on your process of finding solutions:

  1. This is your first version of the diagram and you do not want to throw it away and restart. It makes it more likely that you mix old, maybe irrelevant thoughts with new thoughts.
  2. While you model you think about implementing it. That mixes how you think the model should be and how the programming language wants it to look like. Modeling is not implementing. You should end up with more methods and hopefully more classes in your implementation than your model.
  3. I think the Units class is one way of implementing the desired behavior. It can be let out at your level of granularity. For example, the Player class does not point at a String-subclass PlayerName. Keep one level of granularity in your diagrams.
  4. Starting with a class diagram might not be the choice. An object diagram is more specific. Class diagrams are more general. So I think one can get lost in generalizations that do not need to make sense.
  5. You have different scenarios, play-throughs. You can make several object diagrams from them and generalize from there. No need to put everything in one diagram at once.

To your concern: behavior can be found in several methods Unit.Die() Playfield.RemoveUnit(), Player.KillUnit() at the same time. That is OK. But you want to avoid copy and pasting the code into every method.

About coupling. I think you do/can/must/want not avoid coupling. Some objects should interact. A bad case is when all objects always interact with all other objects in every method. That is too much coupling with no separation of concerns.

And maybe an outdated opinion: OOP is about objects and their interactions, not about classes, not about inheritance.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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