Реализация логической структуры классов для отношений «многие ко многим» и «один ко многим»?

#c# #oop

#c# #ооп

Вопрос:

У меня есть набор из трех классов, которые все связаны друг с другом. В частности, передача, остановка и маршрут.

 Stops can lie on multiple Routes and therefore Routes contain Stops.
Shuttles can exist on one Route at a time, but Routes can contain multiple Shuttles.
A Stop will always exist on at least one Route.
A Shuttle may exist on no Route (though this is avoidable by discarding any Shuttles that are off-Route).
  

Проблема, с которой я сталкиваюсь, заключается в том, как логически связать три класса, чтобы доступ к любому из них был максимально простым.

Каждый экземпляр класса имеет уникальный идентификатор (в пределах своего собственного типа) для ссылки, поэтому я сохраняю все в словарях, чтобы сделать возможным доступ к любому элементу по его идентификатору.

Связь, с которой я начал работать, упрощает выбор конкретного маршрута, но поскольку остановки и шаттлы содержатся в словарях внутри маршрутов. Я должен знать, на каком маршруте находится остановка или шаттл, чтобы ссылаться на него, что невозможно, потому что у меня есть только список доступных мне маршрутов, поэтому было бы необходимо перебирать каждый маршрут, пока я не найду правильную остановку / шаттл.

Каждая альтернатива, которую я придумал, сталкивается с похожими проблемами. Лучшее, что я придумал, это что-то вроде

 class Container
{
    Dictionary<int, Shuttle> shuttles;
    Dictionary<int, Route> routes;
}

class Shuttle
{
    int shuttleId;
    int routeId;
}

class Route
{
    int routeId;
    Dictionary<string, Stop> stops;
}

class Stop
{
    string stopId;
}
  

Однако это только лучшее из-за очень специфической работы кода на данном этапе. Он вычисляет ETA для каждой комбинации остановки / маршрута, которая может быть выполнена путем перебора шаттлов и вычисления времени, которое потребуется этому шаттлу, чтобы добраться до каждой остановки на его текущем маршруте.

Однако у меня есть другие места, где я хотел бы использовать эти классы, например, при отображении карты. Там эта структура ломается, потому что я хотел бы, чтобы при выборе остановки появлялся воздушный шар, показывающий маршруты, с которыми связана остановка. Очевидно, я не могу получить эту информацию напрямую, потому что Stop не знает маршрутов.

Ответ №1:

Вот способ сделать это. Двунаправленные ссылки между объектами требуют немного кода сопровождения для управления изменениями, но вы можете легко перейти от любого экземпляра к любому другому экземпляру.

 // World knows about every entity in it.
// World cares not how entities are related to each other.
public class World
{
  Dictionary<int, Shuttle> shuttles;
  Dictionary<int, Route> routes;
  Dictionary<int, Stop> stops;
}

public class Shuttle
{
  Route CurrentRoute;
}

public class Stop
{
  Dictionary<int, Route> Routes;
}

public class Route
{
  Dictionary<int, Shuttle> Shuttles;
  Dictionary<int, Stop> Stops;
}
  

Например, все челноки удаляются на один маршрут.

 from stop in World.routes[myRouteId].Stops.Values
from route in stop.Routes.Values
where route.Id != myRouteId
from shuttle in route.Shuttles 
select shuttle;
  

Комментарии:

1. Я действительно пытался сделать что-то похожее на это, но я узнал, что это называется циклической зависимостью, и создание такой зависимости, как правило, не одобряется. Я знаю, что всегда есть исключения, это одно из них?

2. У вас есть циклические отношения, например, шаттлы маршрутов остановок моего маршрута. Если вы сериализуете эти типы, вам придется установить IsReference= true для обработки циклов, но в остальном это не имеет большого значения.

3. @Mr.Неоднозначный — Это действительно циклическая зависимость, но если вы будете осторожны с ней, то все в порядке. Это действительно обычная практика в .NET. Как говорит DavidB, «требуется немного кода сопровождения для управления изменениями» — вам просто нужно быть осторожным, чтобы не попасть в бесконечный цикл Остановка-> Маршрут-> Остановка-> Маршрут-> Стоп-> Маршрут и т. Д

Ответ №2:

Измените свой класс контейнера, чтобы предоставить метод для получения всех маршрутов для данной остановки.

 public static class Container
{
    private static Dictionary<int, Shuttle> shuttles;
    private static Dictionary<int, Route> routes;

    /* Other stuff goes on to initialize shuttles and routes I  assume */

    public static List<Route> getRoutes(string stopId)
    {
        List<Route> stopRoutes = new List<Route>();

        foreach (int myKey in routes.Keys)
        {
             foreach (string str in routes[myKey].stops.Keys)
             {
                   if (routes[myKey].stops[str] == stopId)
                   {
                        stopRoutes.Add(routes[myKey]);
                        break;
                   }
             }
         }

         return stopRoutes;
      }
  

Если вы работаете с действительно большими наборами данных или потенциально собираетесь выполнять этот вызов очень часто, вам может понадобиться что-то более эффективное, чем описанное выше. Например, вы делаете метод getRoutes закрытым и поддерживаете словарь, который сопоставляет идентификаторы остановок со списками маршрутов, и обновляете его всякий раз, когда добавляется новый маршрут или новая остановка, чтобы исключить необходимость вызывать getRoutes каждый раз, когда вам нужны маршруты остановки.

Я также предполагаю, что вашему приложению требуется только один экземпляр класса Container, поскольку он представляется просто сосудом для всех данных shuttle / route.