如何组织包(并防止依赖循环)?

时间:2023-01-13 12:22:05

I've been running some metrics on my Java project and apparently there are a lot of dependency cycles between packages. I didn't really know how to organize stuff into packages, so I just did what made sense to me, which is apparently wrong.

我一直在我的Java项目上运行一些指标,显然包之间有很多依赖循环。我真的不知道如何将东西组织成包裹,所以我只是做了对我有意义的事情,这显然是错误的。

My project is a neural network framework. Neural networks have Neurons, which are connected to each other with Connections. They need to depend on each other. However, there are also different types of Neurons, so I thought it'd be a good idea to put them all in there own 'neurons' package. Obviously a Connection isn't a Neuron so it shouldn't be in the package, but since they refer to each other, I now have a circular dependency.

我的项目是一个神经网络框架。神经网络具有神经元,它们通过连接彼此连接。他们需要相互依赖。然而,也有不同类型的神经元,所以我认为将它们全部放在自己的“神经元”包中是个好主意。显然,Connection不是神经元,所以它不应该在包中,但由于它们相互引用,我现在有一个循环依赖。

This is just an example, but I have more situations like this. How do you handle these kinds of situations?

这只是一个例子,但我有更多这样的情况。你如何处理这些情况?

Also, I read that classes in a package higher up in the package hierarchy are not supposed to refer to classes in packages that are deeper. This would mean that a NeuralNetwork class in package 'nn' can not refer to the Neuron in package 'nn.neurons'. Do you guys follow this principle? And what if I would move NeuralNetwork to 'nn.networks' or something? In that case, it would refer to a sibling package instead of a child. Is that better practice?

另外,我读到包层次结构中更高层的包中的类不应该引用更深层的包中的类。这意味着包'nn'中的NeuralNetwork类不能引用包'nn.neurons'中的Neuron。你们遵循这个原则吗?如果我将NeuralNetwork转移到'nn.networks'或其他什么呢?在这种情况下,它将指代一个兄弟包而不是一个孩子。这是更好的做法吗?

5 个解决方案

#1


13  

The antcontrib VerifyDesign task will help you do what you want:

antcontrib VerifyDesign任务将帮助您做您想做的事:

For example, if there are three packages in one source tree

例如,如果一个源树中有三个包

* biz.xsoftware.presentation
* biz.xsoftware.business
* biz.xsoftware.dataaccess

and naturally presentation should only depend on business package, and business should depend on dataaccess. If you define your design this way and it is violated the build will fail when the verifydesign ant task is called. For example, if I created a class in biz.xsoftware.presentation and that class depended on a class in biz.xsoftware.dataaccess, the build would fail. This ensures the design actually follows what is documented(to some degree at least). This is especially nice with automated builds

自然呈现应该只依赖于业务包,业务应该依赖于dataaccess。如果以这种方式定义设计并且违反了,则在调用verifydesign ant任务时,构建将失败。例如,如果我在biz.xsoftware.presentation中创建了一个类,并且该类依赖于biz.xsoftware.dataaccess中的类,则构建将失败。这确保了设计实际遵循记录的内容(至少在某种程度上)。这对于自动构建尤其有用

So once you have decided how things should be organized you can enforce the requirements at compile time. You also get fine-granied control so you can allow certain cases to break these "rules". So you can allow some cycles.

因此,一旦您决定了应该如何组织事情,您就可以在编译时强制执行这些要求。你也可以获得精细控制,这样你就可以允许某些案例打破这些“规则”。所以你可以允许一些周期。

Depending on how you want to do things, you might find that "utils" package makes sense.

根据您的工作方式,您可能会发现“utils”包有意义。

For the particular case that you cite... I might do something like this:

对于你引用的特殊情况......我可能会这样做:

  • package nn contains Nueron and Connection
  • package nn包含Nueron和Connection

  • package nn.neurons contains the subclasses of Nueron
  • package nn.neurons包含Nueron的子类

Neuron and Connection are both high-level concepts used in the NeuralNetowrk, so putting them all together makes sense. The Neuron and Connection classes can refer to each other while the Connection class has no need to know about the Neuron subclasses.

Neuron和Connection都是NeuralNetowrk中使用的高级概念,因此将它们放在一起是有意义的。 Neuron和Connection类可以互相引用,而Connection类不需要知道Neuron子类。

#2


9  

First of all, you are rightfully concerned because circular dependencies between packages are bad. Problems that come out of it grow in importance with the size of the project, but no reason to tackle this situation on time.

首先,你是理所当然的,因为包之间的循环依赖是坏的。随之而来的问题随着项目的规模而变得越来越重要,但没有理由按时解决这种情况。

You should organize your classes by placing classes that you reuse together in the same package. So, if you have for example AbstractNeuron and AbstractConnection, you’d place them in the same package. If you now have implementations HumanNeuron and HumanConnection, you’d place these in the same package (called for example *.network.human). Or, you might have only one type of connection, for example BaseConnection and many different Neurons. The principle stays the same. You place BaseConnection together with BaseNeuron. HumanNeuron in its own package together with HumanSignal etc. VirtualNeuron together with VirtualSignal etc. You say: “Obviously a Connection isn't a Neuron so it shouldn't be in the package..”. This is not that obvious, nor correct to be exact.

您应该通过将在同一个包中重复使用的类放在一起来组织您的类。所以,如果你有例如AbstractNeuron和AbstractConnection,你可以将它们放在同一个包中。如果您现在已经实现了HumanNeuron和HumanConnection,那么您可以将它们放在同一个包中(例如* .network.human)。或者,您可能只有一种类型的连接,例如BaseConnection和许多不同的神经元。原则保持不变。您将BaseConnection与BaseNeuron一起放置。 HumanNeuron与HumanSignal以及VirtualSignal等一起使用自己的包。你说:“显然连接不是神经元所以它不应该在包中......”。这不是那么明显,也不准确。

You say you placed all your neurons in the same package. Neither this is correct, unless you reuse all your implementations together. Again, take a look at scheme I described above. Either your project is so small you place all in the single package, or you start organizing packages as described. For more details take a look at The Common Reuse Principle:

你说你把所有的神经元放在同一个包里。除非您重复使用所有实现,否则这两者都不正确。再次,看看我上面描述的方案。您的项目非常小,您可以将所有项目放在单个包中,或者您开始​​按照描述组织包。有关更多详细信息,请参阅共同重用原则:

THE CLASSES IN A PACKAGE ARE REUSED TOGETHER. IF YOU REUSE ONE OF THE CLASSES IN A PACKAGE, YOU REUSE THEM ALL.

包装中的课程一起重新使用。如果您重新使用一个包中的一个类,您可以重新使用它们。

#3


5  

How do you handle these kinds of situations?

你如何处理这些情况?

Circular dependencies aren't inherently bad. In fact, this can sometimes be a case of the "cure being worse than the disease": extracting an interface increases the level of complexity of your code and adds another layer of indirection. That's probably not worth it for very simple relationships.

循环依赖本身并不坏。事实上,这有时可能是“治愈比疾病更糟糕”的情况:提取界面会增加代码的复杂程度并增加另一层间接性。对于非常简单的关系来说,这可能不值得。

#4


5  

I do not think cyclic dependencies like the ones you describe have to be bad. As long as the concepts that are interdependent are at the same level of abstraction and relate to the same parts of the architecture, it may not be necessary to hide these from each other. Neurons and Connections fit this bill in my understanding.

我不认为像你描述的那样的循环依赖必须是坏的。只要相互依赖的概念处于相同的抽象级别并且与架构的相同部分相关,则可能没有必要将它们彼此隐藏。神经元和连接符合我的理解。

A common to reduce such couplings is to extract interfaces, and possibly even put these in a separate module. Simply organizing by packages inside a single project does not allow you to hide implementation details sufficiently. A common pattern that allows you to really hide implementations is as follows:

减少这种耦合的一个常见方法是提取接口,甚至可能将它们放在一个单独的模块中。简单地通过单个项目内的包进行组织不允许您充分隐藏实现细节。允许您真正隐藏实现的常见模式如下:

Client Code ----> Interfaces <--- Implementation

客户端代码---->接口<---实现

In this pattern, you hide the "Implementation" module from the client code, which means the code in the "Client code" module doesn't even see the implementation code.

在此模式中,您从客户端代码隐藏“实现”模块,这意味着“客户端代码”模块中的代码甚至看不到实现代码。

The nesting of packages serves several purposes: Some projects may have a domain model which is organized in packages. In this case the packages reflect some grouping of the domain, and references may go up/down packages. When it comes to things like implementation of services, your suggested pattern is quite common and a good thing to follow. The deeper in the package hierarchy you get the more specific the class is believed to be.

包的嵌套有多种用途:某些项目可能具有以包的形式组织的域模型。在这种情况下,包反映了域的一些分组,并且引用可以上/下包。当谈到服务实施等问题时,您建议的模式很常见,也是一件好事。包层次结构越深,您认为该类就越具体。

#5


4  

What kind of code-size are we talking about? If you only have 10-20 classes, you probably don't need to (and shouldn't) over-organize your code into packages just for the sake of it.

我们在谈论什么样的代码?如果您只有10-20个类,那么您可能不需要(也不应该)将代码过度组织到包中,只是为了它。

As your project grows, the first distinction you want to make is to separate user-interface code from the underlying datamodel and the logic. Having a cleanly separated layers is crucial in order to be able to do proper unit testing.

随着项目的增长,您要做的第一个区别是将用户界面代码与底层数据模型和逻辑分开。具有干净分离的层对于能够进行适当的单元测试是至关重要的。

If you're having trouble in getting rid of the circular dependencies, it is probably the case the the classes are actually interdependent, and should reside in the same package.

如果您在摆脱循环依赖关系时遇到困难,那么类可能就是这些类实际上是相互依赖的,并且应该位于同一个包中。

Getting the abstraction layers right is probably one of the most important aspects when designing the overall code-structure.

在设计整个代码结构时,获得正确的抽象层可能是最重要的方面之一。

#1


13  

The antcontrib VerifyDesign task will help you do what you want:

antcontrib VerifyDesign任务将帮助您做您想做的事:

For example, if there are three packages in one source tree

例如,如果一个源树中有三个包

* biz.xsoftware.presentation
* biz.xsoftware.business
* biz.xsoftware.dataaccess

and naturally presentation should only depend on business package, and business should depend on dataaccess. If you define your design this way and it is violated the build will fail when the verifydesign ant task is called. For example, if I created a class in biz.xsoftware.presentation and that class depended on a class in biz.xsoftware.dataaccess, the build would fail. This ensures the design actually follows what is documented(to some degree at least). This is especially nice with automated builds

自然呈现应该只依赖于业务包,业务应该依赖于dataaccess。如果以这种方式定义设计并且违反了,则在调用verifydesign ant任务时,构建将失败。例如,如果我在biz.xsoftware.presentation中创建了一个类,并且该类依赖于biz.xsoftware.dataaccess中的类,则构建将失败。这确保了设计实际遵循记录的内容(至少在某种程度上)。这对于自动构建尤其有用

So once you have decided how things should be organized you can enforce the requirements at compile time. You also get fine-granied control so you can allow certain cases to break these "rules". So you can allow some cycles.

因此,一旦您决定了应该如何组织事情,您就可以在编译时强制执行这些要求。你也可以获得精细控制,这样你就可以允许某些案例打破这些“规则”。所以你可以允许一些周期。

Depending on how you want to do things, you might find that "utils" package makes sense.

根据您的工作方式,您可能会发现“utils”包有意义。

For the particular case that you cite... I might do something like this:

对于你引用的特殊情况......我可能会这样做:

  • package nn contains Nueron and Connection
  • package nn包含Nueron和Connection

  • package nn.neurons contains the subclasses of Nueron
  • package nn.neurons包含Nueron的子类

Neuron and Connection are both high-level concepts used in the NeuralNetowrk, so putting them all together makes sense. The Neuron and Connection classes can refer to each other while the Connection class has no need to know about the Neuron subclasses.

Neuron和Connection都是NeuralNetowrk中使用的高级概念,因此将它们放在一起是有意义的。 Neuron和Connection类可以互相引用,而Connection类不需要知道Neuron子类。

#2


9  

First of all, you are rightfully concerned because circular dependencies between packages are bad. Problems that come out of it grow in importance with the size of the project, but no reason to tackle this situation on time.

首先,你是理所当然的,因为包之间的循环依赖是坏的。随之而来的问题随着项目的规模而变得越来越重要,但没有理由按时解决这种情况。

You should organize your classes by placing classes that you reuse together in the same package. So, if you have for example AbstractNeuron and AbstractConnection, you’d place them in the same package. If you now have implementations HumanNeuron and HumanConnection, you’d place these in the same package (called for example *.network.human). Or, you might have only one type of connection, for example BaseConnection and many different Neurons. The principle stays the same. You place BaseConnection together with BaseNeuron. HumanNeuron in its own package together with HumanSignal etc. VirtualNeuron together with VirtualSignal etc. You say: “Obviously a Connection isn't a Neuron so it shouldn't be in the package..”. This is not that obvious, nor correct to be exact.

您应该通过将在同一个包中重复使用的类放在一起来组织您的类。所以,如果你有例如AbstractNeuron和AbstractConnection,你可以将它们放在同一个包中。如果您现在已经实现了HumanNeuron和HumanConnection,那么您可以将它们放在同一个包中(例如* .network.human)。或者,您可能只有一种类型的连接,例如BaseConnection和许多不同的神经元。原则保持不变。您将BaseConnection与BaseNeuron一起放置。 HumanNeuron与HumanSignal以及VirtualSignal等一起使用自己的包。你说:“显然连接不是神经元所以它不应该在包中......”。这不是那么明显,也不准确。

You say you placed all your neurons in the same package. Neither this is correct, unless you reuse all your implementations together. Again, take a look at scheme I described above. Either your project is so small you place all in the single package, or you start organizing packages as described. For more details take a look at The Common Reuse Principle:

你说你把所有的神经元放在同一个包里。除非您重复使用所有实现,否则这两者都不正确。再次,看看我上面描述的方案。您的项目非常小,您可以将所有项目放在单个包中,或者您开始​​按照描述组织包。有关更多详细信息,请参阅共同重用原则:

THE CLASSES IN A PACKAGE ARE REUSED TOGETHER. IF YOU REUSE ONE OF THE CLASSES IN A PACKAGE, YOU REUSE THEM ALL.

包装中的课程一起重新使用。如果您重新使用一个包中的一个类,您可以重新使用它们。

#3


5  

How do you handle these kinds of situations?

你如何处理这些情况?

Circular dependencies aren't inherently bad. In fact, this can sometimes be a case of the "cure being worse than the disease": extracting an interface increases the level of complexity of your code and adds another layer of indirection. That's probably not worth it for very simple relationships.

循环依赖本身并不坏。事实上,这有时可能是“治愈比疾病更糟糕”的情况:提取界面会增加代码的复杂程度并增加另一层间接性。对于非常简单的关系来说,这可能不值得。

#4


5  

I do not think cyclic dependencies like the ones you describe have to be bad. As long as the concepts that are interdependent are at the same level of abstraction and relate to the same parts of the architecture, it may not be necessary to hide these from each other. Neurons and Connections fit this bill in my understanding.

我不认为像你描述的那样的循环依赖必须是坏的。只要相互依赖的概念处于相同的抽象级别并且与架构的相同部分相关,则可能没有必要将它们彼此隐藏。神经元和连接符合我的理解。

A common to reduce such couplings is to extract interfaces, and possibly even put these in a separate module. Simply organizing by packages inside a single project does not allow you to hide implementation details sufficiently. A common pattern that allows you to really hide implementations is as follows:

减少这种耦合的一个常见方法是提取接口,甚至可能将它们放在一个单独的模块中。简单地通过单个项目内的包进行组织不允许您充分隐藏实现细节。允许您真正隐藏实现的常见模式如下:

Client Code ----> Interfaces <--- Implementation

客户端代码---->接口<---实现

In this pattern, you hide the "Implementation" module from the client code, which means the code in the "Client code" module doesn't even see the implementation code.

在此模式中,您从客户端代码隐藏“实现”模块,这意味着“客户端代码”模块中的代码甚至看不到实现代码。

The nesting of packages serves several purposes: Some projects may have a domain model which is organized in packages. In this case the packages reflect some grouping of the domain, and references may go up/down packages. When it comes to things like implementation of services, your suggested pattern is quite common and a good thing to follow. The deeper in the package hierarchy you get the more specific the class is believed to be.

包的嵌套有多种用途:某些项目可能具有以包的形式组织的域模型。在这种情况下,包反映了域的一些分组,并且引用可以上/下包。当谈到服务实施等问题时,您建议的模式很常见,也是一件好事。包层次结构越深,您认为该类就越具体。

#5


4  

What kind of code-size are we talking about? If you only have 10-20 classes, you probably don't need to (and shouldn't) over-organize your code into packages just for the sake of it.

我们在谈论什么样的代码?如果您只有10-20个类,那么您可能不需要(也不应该)将代码过度组织到包中,只是为了它。

As your project grows, the first distinction you want to make is to separate user-interface code from the underlying datamodel and the logic. Having a cleanly separated layers is crucial in order to be able to do proper unit testing.

随着项目的增长,您要做的第一个区别是将用户界面代码与底层数据模型和逻辑分开。具有干净分离的层对于能够进行适当的单元测试是至关重要的。

If you're having trouble in getting rid of the circular dependencies, it is probably the case the the classes are actually interdependent, and should reside in the same package.

如果您在摆脱循环依赖关系时遇到困难,那么类可能就是这些类实际上是相互依赖的,并且应该位于同一个包中。

Getting the abstraction layers right is probably one of the most important aspects when designing the overall code-structure.

在设计整个代码结构时,获得正确的抽象层可能是最重要的方面之一。