Java 如何组织包(并防止依赖循环)?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/670948/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to organize packages (and prevent dependency cycles)?
提问by Jordi
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.
我的项目是一个神经网络框架。神经网络有神经元,它们通过连接相互连接。他们需要相互依赖。然而,也有不同类型的神经元,所以我认为将它们全部放在自己的“神经元”包中是个好主意。显然连接不是神经元,所以它不应该在包中,但由于它们相互引用,我现在有一个循环依赖。
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”包中的神经元。你们遵守这个原则吗?如果我将 NeuralNetwork 移动到 'nn.networks' 或其他东西会怎样?在这种情况下,它将引用兄弟包而不是子包。这是更好的做法吗?
采纳答案by TofuBeer
The antcontrib VerifyDesign taskwill 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
例如,如果在一个源代码树中有三个包
* biz.xsoftware.presentation * biz.xsoftware.business * biz.xsoftware.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.neurons contains the subclasses of Nueron
- 包 nn 包含 Nueron 和 Connection
- 包 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 子类。
回答by krosenvold
I do not think cyclic dependencies like the ones you describe haveto 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.
包的嵌套有几个目的: 一些项目可能有一个以包组织的域模型。在这种情况下,包反映了域的某些分组,并且引用可能会上升/下降包。当涉及到服务实现之类的事情时,您建议的模式非常普遍,值得遵循。包层次结构越深,您就越认为该类越具体。
回答by JesperE
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 data model and the logic. Having 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.
在设计整体代码结构时,正确设置抽象层可能是最重要的方面之一。
回答by John Feminella
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.
循环依赖本身并不是坏事。事实上,这有时可能是“治愈比疾病更糟糕”的情况:提取接口会增加代码的复杂程度并增加另一层间接性。对于非常简单的关系,这可能不值得。
回答by Dan
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 等一起在自己的包中。 VirtualNeuron 与 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.
包中的类可以一起重用。如果您重复使用一个包中的一个类,您将重复使用它们。