【问题标题】:In C# or OOP, should 2 classes reference each other that are related?在 C# 或 OOP 中,两个类是否应该相互引用相关的?
【发布时间】:2011-01-23 18:36:49
【问题描述】:

我正在使用 C# 开发一个类库。我设计了 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 的分离,因为它允许独立修改和测试代码。另外,我觉得它使每个部分更小更简单。

任何可以提供的建议将不胜感激。

更新:代码块已更新,以提供有关代码的更多详细信息。

【问题讨论】:

  • 您为什么不精简您想要使用的实际代码并向我们展示呢?这样的事情真的取决于情况。

标签: c# .net asp.net oop


【解决方案1】:

如果doSomething() 总是需要对C 对象的父对象的引用,您确实应该将此引用放入C 中,以便确保它引用正确的B 实例。 OTOH,如果该引用并不总是父引用,但它仍然总是引用同一个B 实例,它仍然建议将其转换为C 的成员。 OTOH,如果可以使用不同的引用调用 doSomething(),则该引用应保留为方法参数。

从子级到父级的引用本身并不坏,或者在两个类之间具有相互依赖关系 - 这取决于上下文。这样做的后果是这两个类不能单独使用,所以实际上它们形成了一个组件。这对您来说可能接受也可能不接受。

组件通常可以由多个类组成——一个包含其项目和迭代器的集合实际上就是一个典型的例子。但是,建议在物理级别上表达这些类之间的逻辑依赖关系,例如通过使一个类成为另一个类的内部类,或使两个类成为第三个类的内部类。

【讨论】:

    【解决方案2】:

    这真的取决于情况。一般来说,除非“B”类和“C”类之间存在明确、明显的关系,否则C.doSomething() 需要访问B 是一个危险信号,因为C 包含在 B ...

    但是,B 中需要访问 C 的方法是有意义的,因为 C 是 B 中的成员。

    话虽如此,有时这是合适的。在不了解您的实际课程以及它们所代表的内容的情况下,很难说更多...

    【讨论】:

    • 问题已更新以提供更多详细信息。我同意红旗评论。虽然我可以让代码工作,但我觉得有些不对劲。
    • @Swoop:在您的情况下,Billing 似乎应该是 Customer 的一部分,并且 Billing.doSomething 应该传递一个 PolicyTerm 作为参数......
    【解决方案3】:

    两个类不应该,但是两个接口是可以的。

    当然,接口越小越好。您会发现,如果接口足够小(它们应该是 - 请参阅 Interface Segregation Principal),您实际上不需要两个相同的接口。

    【讨论】:

      【解决方案4】:

      创建对所需类的引用似乎并不是一个坏主意。如果需要,您可以让 Class C 的构造函数获取 Class B 的引用并将其存储在成员变量中。

      我目前正在做一个项目,其中几个类的行为是这样的。

      另一个可能更“理智”的选择是在 C 类上设置一个事件,类似于“SuchAndSuchDataRequired”。然后,当 B 类获取 C 的实例时,B 类可以监听该事件。当 C 类需要来自 B 的数据时,从 doSomething() 中触发事件,然后 B 在其事件处理程序和宾果游戏中返回数据 - C 类具有数据,甚至不知道它来自 B 类。

      【讨论】:

      • 我喜欢事件的概念,但这可能比它需要的更复杂。同样,这完全取决于他到底想做什么。
      【解决方案5】:

      一般的经验法则是让数据尽可能靠近将要使用它的函数/方法/类。这将使事情保持解耦,并且您不必让两个类相互引用,这实际上使您必须创建一个可能不需要的额外对象。

      正如 ChaosPandion 所说:请发布一些更具体的代码,以便我们更好地帮助您。

      编辑:

      如果您 B 引用 C 并且 C 引用 B,那么您可能需要考虑将两者放在一起作为一个对象。如果两个类不完全不同,这种方法效果最好。如果没有真正可区分的区别,那么只需将它们放在一个类中......这可以简化整个事情。

      【讨论】:

      • 更新了代码以提供更多详细信息。我同意 PolicyTerm(B 类)和 Billing(C 类)彼此密切相关,但我觉得将它们作为单独的类很好。
      【解决方案6】:

      在我看来,您的建模似乎有点歪斜,即为什么在策略中存在人员类型的属性以及为什么在策略中具有具体实现的列表,即 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;
           }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-07-15
        • 1970-01-01
        • 1970-01-01
        • 2019-05-03
        • 2012-04-22
        相关资源
        最近更新 更多