В С# или ООП, должно ли 2 класса ссылаться друг на друга, которые связаны?

Я работаю над библиотекой классов, использующей С#. Я разработал 3 основных класса, чтобы помочь моделировать наши данные. Они разработаны таким образом, что класс A содержит список экземпляров класса B, а класс B содержит ссылку на экземпляр класса C, то есть:

public class Policy
{
    public List < PolicyTerm > myTerms;
    person Customer;
    string PolicyNumber;
}

public class PolicyTerm
{
     public Billing myBill;
     Datetime effectivedate;
     List < Activities > termActivities;
     public doAction()
     {
          use value from Policy, like PolicyNumber;
     }

}

public class Billing
{
    float remainingBalance;
    Datetime nextDueDate;
    public void doSomething()
    {
         reference value from PolicyTerm, such as effective date;
         use value from Policy, such as PolicyNumber;
    }
}

Проблема заключается в том, что я пытаюсь использовать метод в PolicyTerm или Billing, который нуждается в данных из содержащего класса. В приведенном выше примере это будет метод "doSomething", пытающийся использовать значение из PolicyTerm, например, дату вступления в силу для запроса или сохранения данных в нашей базе данных.

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

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

Любые советы, которые могут быть предоставлены, будут высоко оценены.

Обновление: блок кода был обновлен, чтобы предоставить более подробную информацию о коде.

Ответы

Ответ 1

Если doSomething() всегда требуется ссылка на родительский объект C, вы должны поместить эту ссылку в C, где вы можете убедиться, что она относится к правильному экземпляру B. OTOH, если эта ссылка не всегда является родительской, но все же она всегда будет ссылаться на один и тот же экземпляр B, он все же предлагает превратить его в член C. OTOH, если doSomething() можно вызвать с различными ссылками, эта ссылка должна храниться как параметр метода.

Неплохо поместить ссылку от ребенка к родительскому или иметь взаимную зависимость между двумя классами - это зависит от контекста. Следствием этого является то, что два класса нельзя использовать отдельно, поэтому на самом деле они образуют компонент. Это может быть или не быть приемлемым для вас.

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

Ответ 2

Это действительно зависит от ситуации. В общем случае, если между классами "B" и "C" нет ясной, очевидной связи, это красный флаг, которому C.doSomething() потребуется доступ к B, поскольку C содержится внутри B...

Однако метод в B, требующий доступа к C, имеет смысл, поскольку C является членом внутри B.

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

Ответ 3

Два класса не должны, но два интерфейса в порядке.

Конечно, чем меньше интерфейсы, тем лучше. Вы обнаружите, что если интерфейсы достаточно малы (они должны быть - см. Принцип разделения интерфейса), вам фактически не понадобится 2 того же.

Ответ 4

Создание ссылки на ваш необходимый класс вовсе не кажется плохим. Если это необходимо, вы можете сделать конструктор класса C ссылкой на класс B и сохранить его в переменной-члене.

Я работаю над проектом на данный момент, когда пара классов ведет себя так.

Другой вариант, который может быть немного более "нормальным", - это иметь событие на классе C, что-то вроде "SuchAndSuchDataRequired". Класс B мог тогда прослушать это событие, когда он получает экземпляр C. Класс C запускает событие изнутри doSomething(), когда ему нужны данные из B, B, затем возвращает данные в этом обработчике событий, а бинго-класс C имеет данные и даже не знают, что это произошло из класса B.

Ответ 5

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

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

Edit:

Если вы ссылаетесь на ссылки C и C B, то вы можете рассмотреть возможность объединения этих двух объектов в один объект. Это лучше всего работает, если два класса не совсем разные. Если нет реальной различимой разницы, тогда просто соедините ее в одном классе..., что может упростить все это.

Ответ 6

По-моему, ваше моделирование кажется немного искаженным, то есть почему существует свойство типа person внутри политики и почему есть список конкретной реализации в политике, то есть PolicyTerm. Это объединяет классы и не чувствует себя правильно - т.е. Политика. У клиента есть клиент? Если у Клиента есть политика

Могу ли я предложить следующее (быстро смоделированное и не протестированное, но вы должны видеть, что я получаю)

public class Customer()
{
    prop name,etc,etc;
    public List<IPolicyTerm> Policies{get;set;}//I use public getters and setters throughout but you need to choose what level of encapsulation you want
    private Account customerAccount{get;set}        

    public Customer()
    {
        //ctor
        customerAccount = doDbCall;
        Policies = doDbCall;
    }

    public decimal GetCurrentPolicyCost()
    {
        decimal cost = 0;
        foreach(var policy in Policies)
        {
            if(policy.DueDate < DateTime.Now){
            cost += policy.GetCost(); //for example but you can call whatever is defined at the interface level
            }
        }
        return cost;
    }

    public bool HasEnoughFunds()
    {
        return customerAccount.Balance >= GetCurrentPolicyCost();
    }

    //keeping Account hidden in Person as Person has a reference to Account. 
    //By doing so there is type coupling between the two classes 
    //BUT you can still modify Policies away from Person
    private class Account
    {
       //should only contain properties and I assume only one 'Account' per person
    }
}

public interface IPolicyTerm
{
     object Id{get;set}
     DateTime DueDate {get;set;}
     decimal GetCost();
}

///now we can have polymorphic Policies i.e. the cost of one can be calculated differently based on policy

public class LifeCoverPolicy : IPolicyTerm
{

     public object Id;
     public DateTime DueDate{get;set;}         

     public decimal GetCost()
     {
          return 10;
     }
}