0

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

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

担心这个的人认为这是上面提到的不好的做法,并试图根本不这样做,尽管我很少看到服务器,尤其是大型服务器,将所有数据包处理保留在客户端类中。此外,您可能会比处理程序更进一步,并调用更多类。将所有内容保留在客户端内部是一团糟。那么……真的是不好的做法吗?

如果是,人们将如何绕过它?要完成此操作,您需要在 Client 中或多或少复杂的对象集合,您可以将其传递下来,而无需再次调用 Client 的函数......

就像我说的,我无法真正解决这个“问题”。有人可以帮我吗?

4

4 回答 4

5

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

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

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

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

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

在这种情况下,数据包处理程序现在绑定到客户端的实现。它要求客户端有一个 GetCredentials() 方法和一个 MessageReceived 方法来回调它。现在想象一个更加解耦的场景:

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

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

于 2012-04-18T07:28:38.123 回答
3

在某些情况下,循环类引用一点也不坏。总是存在实例化问题(鸡或蛋)。永远记住,对象之间已经存在两种通信方式。您可以通过调用一个在有新消息时返回的函数来让客户端等待下一条消息。

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

  • 当你通知一个班级时,一些动作已经发生->让它成为一个事件。
  • 当它是一对一的关系时->是否应该将类型合并为一个。
  • 当您的类扩展另一个的功能时->从它继承而不是传递引用。

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

  • 客户端 -> 套接字(负责网络的东西)
  • ClientHandler(负责控制流程)
    • 客户
    • 包处理程序
    • 附加信息
  • 包处理程序
于 2012-04-18T07:29:19.103 回答
2

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

跨包边界,内聚自然较低,耦合应尽可能低:绝对没有循环关系。

于 2012-04-18T09:48:27.190 回答
0

You can try to extract the functionalities that A calls on B and the ones that B calls on A and then encapsulate them in a Class Library C, that the other libraries can invoke without problems. Obviously, C mustn't have reference neither to A nor B.

于 2012-04-18T07:03:27.690 回答