Home > database >  How to break this circular dependency
How to break this circular dependency

Time:10-19

I have two classes (Room, Exit) that I think should depend on each other but that causes circular dependency which is a bad thing from what I read.

//problem
public class Game
{
   public List<Room> Rooms {get; set;}
}

public class Room
{
   public Exit Exit {get; set;}
}

public class Exit
{
   public Room NextRoom {get; set;}
}

//Here is my solution

public class Game
{
   public List<Room> Rooms {get; set;}

   public Room GetNextRoom(Exit exit)
   {
      //Loops through the rooms list and compares Room.Id by Exit.NextRoomId and returns it
   }
}
public class Room
{
   public Exit Exit {get; set;}
}

public class Exit
{
   public string NextRoomId {get;set;}
}

My question is if my solution is somewhat right or if there is another better solution to this problem. This breaks the circular dependency but makes it ugly when declaring the Exit object as it "references" the Room object by a string.

Keep in mind that I would like to not implement an Interface or Eventhandlers which I read is basically just a band-aid on the problem and can cause problems in the future.

CodePudding user response:

What you have is effectively a directed graph with cycles. Your model of this is correct - you can't get rid of the cycles.

However, you don't need a separate "Exit" class - you can represent the exits for each room with a List<Room> property called (say) Exits.

Aside: Because the exits are effectively "directed" (i.e. they point to the next room only) to represent a bidirectional exit between room 1 and room 2 you have to add each exit individually. That is, when adding an exit from room 1 to room 2, you must also add an exit from room 2 to room 1 (unless the exits are one-way, of course).

You can use Newtonsoft.Json to serialise and deserialise such a graph, accounting for circular references.

The key thing is that you need to specify the Json serialisation option PreserveReferencesHandling = PreserveReferencesHandling.All.

Another important fact is that you cannot have any constructors that set properties of the class - otherwise the serialisation/deserialisation will fail.

Here's a compilable console app to demonstrate how to do it properly:

using System;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace ConsoleApp1
{
    static class Program
    {
        public static void Main()
        {
            var room1 = new Room { Name = "room1" };
            var room2 = new Room { Name = "room2" };
            var room3 = new Room { Name = "room3" };

            room1.Exits.Add(room2); room2.Exits.Add(room1);
            room2.Exits.Add(room3); room3.Exits.Add(room2);
            room3.Exits.Add(room1); room1.Exits.Add(room3);

            var jsonSettings = new JsonSerializerSettings
            {
                PreserveReferencesHandling = PreserveReferencesHandling.Objects
            };

            var serialised   = JsonConvert.SerializeObject(room1, jsonSettings);
            var deserialised = JsonConvert.DeserializeObject<Room>(serialised, jsonSettings)!;

            Console.WriteLine(deserialised.Name);                   // "room1"
            Console.WriteLine(deserialised.Exits[0].Name);          // "room2"
            Console.WriteLine(deserialised.Exits[0].Exits[1].Name); // "room3"
        }
    }

    public sealed class Room
    {
        public string Name { get; init; }
        public List<Room> Exits { get; } = new ();
    }
}

Note that the printing out of "room3" uses Exits[1] because that's how it was wired up in the first place.

CodePudding user response:

if a exit has no other properties or any properties that a room wouldn't (which I don't know why a exit would be different than a room since a exit would just be a pointer to the next room or null if your at the end of the game) then I would just do this, where the room holds a reference to the next room. Note: This assumes a room has only one exit, if there are multiple see mathews answer.

    public class Game
    {
        public List<Room> Rooms { get; set; }
    }

    public class Room
    {
        public int RoomNumber { get; set; }
        public Room Exit { get; set; }
    }

you can then use it like this

    static void Main(string[] args)
    {
        Game game = new Game();
        game.Rooms = new List<Room>();

        Room firstRoom = new Room();
        Room exit = new Room();
        firstRoom.RoomNumber = 1;
        exit.RoomNumber = 2;

        firstRoom.Exit = exit;

        game.Rooms.Add(firstRoom);

        foreach(Room room in game.Rooms)
        {
            Console.WriteLine("Room:"   room.RoomNumber);
            Console.WriteLine("Exit:"   room.Exit.RoomNumber);
        }

        Console.ReadKey();
    }
  • Related