繁体   English   中英

循环类引用不良做法?

[英]Circular class references bad practice?

最近有人告诉我,以Class 1调用Class 2调用Class 1形式的循环引用被认为是极端不好的做法,而我只是无法理解。 我的意思是,如果这些类位于两个不同的项目中,那么我会完全理解问题所在,但是如果它们属于同一项目,那会很糟糕吗? 在某些情况下...您到底如何防止这种情况发生?

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

关心这个问题的人以为这是提到的不良做法,尽管我很少见到服务器,尤其是大型服务器,但这些服务器将所有数据包处理都保留在客户端类中,但他根本没有这样做。 此外,您可能会比处理程序走得更远,并调用更多的类。 将所有内容保留在客户端中一团糟。 所以... 真的是不好的做法吗?

如果是这样,人们将如何解决呢? 为了做到这一点,您需要在Client中添加或多或少复杂的对象集合,您可以将其传递下来,而不必再次调用Client函数...

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

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

但是您指的不是项目结构,而是体系结构,这里的事情要复杂一些。 相互调用的类没有内在的错。 实际上,这在.NET中的回调和事件等功能的设计中是隐含的-当您注册事件时,实际上是在调用一个类,该类随后将使用事件处理程序进行回调。

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

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

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

在这种情况下,数据包处理程序现在绑定到客户端的实现。 它要求客户端具有GetCredentials()方法和MessageReceived方法以对其进行回调。 现在想象一个更分离的场景:

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

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

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

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

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

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

  • 客户端->套接字(负责网络事务)
  • ClientHandler(负责控制流程)
    • 客户
    • 包处理程序
    • 附加信息
  • 包处理程序

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

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

您可以尝试提取A调用B的功能以及B调用A的功能,然后将它们封装在类库C中,其他库可以毫无问题地调用它们。 显然,C不能同时引用A和B。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM