循环类引用不好的做法?

时间:2012-04-18 06:50:00

标签: c# oop dependencies

最近有人告诉我,Class 1形式的循环引用如何调用Class 2调用Class 1被认为是非常糟糕的做法,而我无法理解这一点。我的意思是,如果这些课程在2个不同的项目中,我完全理解这个问题,但如果他们在同一个项目中,那可能会很糟糕吗?在某些情况下......你究竟是怎么防止这种情况的呢?

例如:我有一台某种服务器。客户端连接到它,客户端从套接字派生或持有它,负责网络内容,以及一些信息,如帐户ID等。当有新的东西时,客户端调用数据包处理程序,现在数据包处理程序需要来自客户端的信息,并且必须返回信息。我将客户端传递给数据包处理程序,因此它可以调用它的发送函数等。

关注这个问题的人认为这是上面提到的不良做法,尽管我很少见到服务器,尤其是大型服务器,但他们将所有数据包处理保留在客户端类中。此外,您可能会比处理程序更进一步,并调用更多类。将所有内容保留在客户端内是一团糟。那么...... 这个真的很糟糕吗?

如果是的话,怎么会绕过它呢?要完成这项工作,您需要在客户端中使用或多或少复杂的对象集合,您可以向下传递,而无需再次调用客户端的功能......

像我说的那样,我无法真正地围绕这个“问题”。有人可以帮助我吗?

4 个答案:

答案 0 :(得分:5)

当人们谈论循环引用时,他们通常会讨论项目/ dll引用。在解析引用时这些都是有问题的,并且Visual Studio不允许您添加循环引用。

但是你指的不是项目结构,而是指架构,这里的事情有点复杂。类相互调用没有任何内在错误。实际上,这在.NET的回调和事件等功能的设计中是隐含的 - 当您注册到事件时,您实际上正在调用一个类,该类稍后将使用事件处理程序回调您。

然而,这种形式的循环呼叫相对分离。服务器没有对客户端的EXPLICIT引用,但只有被调用的订阅客户端列表。如果您还没有这样做,而是拥有数据包处理程序,例如,保持对客户端的显式引用,那么这两个类将紧密耦合 - 数据包处理程序依赖于客户端的特定实现,反之亦然。

为什么这么糟糕?在我看来,这违反了关注点分离原则,这是编程中最基本的概念之一。客户端应该知道如何处理客户端操作,数据包处理程序应该处理数据包操作,既不应该知道另一个是如何工作的,也只能通过明确定义的特定接口进行通信。

让我们采取一个基于您的OP的非常精简的假设情况,该情况具有循环引用: 客户端调用数据包处理程序的Send()方法。数据包处理程序现在启动连接,然后发现它需要用户名/密码。它调用客户端上的方法获取它,然后将其发送到服务器,获取响应,然后回调客户端返回它。

在这种情况下,数据包处理程序现在与客户端的实现相关联。它要求客户端使用GetCredentials()方法和MessageReceived方法来回调它。现在想象一个更加分离的场景:

客户端首先注册处理程序的ResponseReceived事件。现在客户端调用数据包处理程序的Send()方法。数据包处理程序需要身份验证,因此它会失败 - 它会抛出异常或返回错误代码,说“无法连接”。客户端获取此响应,并再次调用,这次使用Send(用户名,密码)方法。它成功,获得响应,并引发ResponseReceived事件,将响应发送给订阅它的任何人。

这允许数据包处理程序在其他客户端的其他上下文中重用。它允许在客户端或处理程序内部进行更改,而对其他组件的影响较小。它使代码更简单,更容易维护。这很好。 :)

答案 1 :(得分:3)

有些情况下,循环类引用并不是坏事。总有一个实例化问题(鸡肉或鸡蛋)。永远记住,对象之间已经存在双向通信。您可以通过调用在有新消息时返回的函数使客户端等待nextmessage。

但是你应该停下来思考一下。特别是在以下情况下(还有更多,但这些都在我的头脑中):

  • 当您通知课程时,某些操作已经发生 - > 使其成为事件。
  • 如果是一对一关系 - > 应将这些类型合并为一个。
  • 当您的类扩展其他 - > 的功能时,不会传递引用。

在您的情况下,客户< - >包处理程序。我可能不会在那里保留循环引用。我可能会为这两个类编写一个控制器,它们充当它们之间信息的代理。我也不会将帐户ID存储在客户端对象中。它有点违背我要遵守的单一责任原则。我可能最终得到这样的东西:

  • 客户 - > socket(负责网络的东西)
  • ClientHandler(负责控制流程)
    • 客户端
    • PacketHandler
    • 其他信息
  • PacketHandler

答案 2 :(得分:2)

同一中的类应该被认为具有高度凝聚力(假设您的包结构正确!)并且可以具有更紧密的耦合。在包中,循环关系 - 如果需要 - 可以,但是将受益于基于特定接口的设计(如之前的海报所述)。

跨越包边界,凝聚力自然更低,耦合应保持尽可能低:绝对没有循环关系。

答案 3 :(得分:0)

您可以尝试提取A调用B的函数和B调用A的函数,然后将它们封装在类库C中,其他库可以毫无问题地调用它们。显然,C必须既不能引用A也不能引用B.