接口应该放在单独的包中吗?

我是一个从事大型项目的团队的新成员,这个项目有很多组件和依赖项。对于每个组件,都有一个 interfaces包,其中放置了该组件的公开接口。这是个好习惯吗?

我通常的做法是接口和实现放在同一个包中。

64915 次浏览

Yes, it's a very good practice, because it allows you to publish the interface without publishing your specific implementation. That said, if you have no need to publish external interfaces, there's no problem with putting the interface definitions in the same package as the implementation.

For any language, putting them together in the same package is fine. The important thing is what's exposed to the outside world, and how it looks from outside. Nobody's going to know or care if the implementation is in that same package or not.

Let's look at this particular instance.

If you have all public things in one package, and private things in another package that is not publicly exposed, the client of the library sees one package. If you move the private things to the package with the publicly exposed things, but do not expose them from within the package, the client sees exactly the same thing.

Thus, this has the smell of a rule with no good reason: it's making a decision based on something being publicly visible without that decision having any effect on what's publicly visible.

That said, if in any particular instance it seems like a good idea to split the interface and implementation in to separate packages, go right ahead and do that. Reasons for doing this that come to mind are that the package is huge, or you have an alternate implementation you might want to link in instead of the standard one.

We do this where I work (ie: put interface in one package and implementation in another) and the main advantage we get out of this is we can easily swap between implementations.

I haven't much Java experience, but I like to do this as a good practice in C#/.NET because it allows for future expansion where the assemblies with the concrete classes which implement the interfaces may not be distributed all the way down to the client because they are proxied by a middleman factory or across the wire in a web service scenario.

One argument for putting interfaces in different packages is that it is easier to create 'api' jars that can be distributed to consumers of your product or service. It's perfectly possible to do this with interfaces and implementations together, but simpler to script if they are in different packages.

Placing both the interface and the implementation is common place, and doesn't seem to be a problem.

Take for example the Java API -- most classes have both interfaces and their implementations included in the same package.

Take for example the java.util package:

It contains the interfaces such as Set, Map, List, while also having the implementations such as HashSet, HashMap and ArrayList.

Furthermore, the Javadocs are designed to work well in those conditions, as it separates the documentation into the Interfaces and Classes views when displaying the contents of the package.

Having packages only for interfaces may actually be a little bit excessive, unless there are enormous numbers of interfaces. But separating the interfaces into their own packages just for the sake of doing so sounds like bad practice.

If differentiating the name of a interface from an implementation is necessary, one could have a naming convention to make interfaces easier to identify:

  • Prefix the interface name with an I. This approach is taken with the interfaces in the .NET framework. It would be fairly easy to tell that IList is an interface for a list.

  • Use the -able suffix. This approach is seen often in the Java API, such as Comparable, Iterable, and Serializable to name a few.

Put them in packages that reflect your projects. It is fine and common to put interfaces and implementations together if they're part of the same project, but if you're writing an API, then someone else would likely be choosing a package name relevant to their project.

In general, if it's for the same project, I don't see any benefit in keeping the interfaces in a separate package from their impls. If it's getting cluttered, there may be other package naming issues, irrespective of the interface arrangement.

In many frameworks, such as OSGi, you almost have to. I think this promotes looser coupling, at the package instead of the jar level.

I prefer them in the same package. Feels pointless to create a package specifically for interfaces. This is especially redundant for teams that simply love their interface prefixes and suffixes (e.g. the "I", and "Impl" for Java).

If there's a needs to publish a set of interfaces as public API, it makes more sense to keep them in an entirely separate project and create project dependencies instead. But it all boils down to preference and convenience of the situation I suppose.

I normally put Interfaces with the implementation however I can sort of see why you may want to keep them separate. Say, for instance, someone wanted to reimplement classes based upon your interfaces, they would need a jar/lib/etc with your implementation rather than just the interfaces. With them separate you could just say "Here's my implementation of that interface" and be done with it. Like I said, not what I do but I can sort of see why some might want to.

There are a lot of good reasons for putting interfaces in a separate package from implementation. For example you may want to use container that supports Dependency Injection to wire up the necessary implementation(s) at run time, in which case only the interface needs to be present at build time and the implementation can be supplied at run time. Alternatively, you may want to provide multiple implementations (for example using mock implementations for testing) or multiple versions of a particular implementation at run time (for example for A/B testing, etc). For these kinds of use cases it's more convenient to package interface and implementation separately.

I think it is important to note that for the OSGi framework, it is nicer that they are in different packages so that you can easily export a whole package while hiding the implementation packages.

I'm doing this on a project and it is working well for me because of these reasons:

  • Separately packaged interfaces and implementations are for a different use case than for the various types of Map or Set. There is no reason to have a package just for trees (java.util.tree.Map, java.util.tree.Set). It is just a standard datastructure, so put it together with the other datastructures. However, if you are dealing with a puzzle game that has a really simple debug interface and a pretty production interface, as part of its front-end you might have a com.your.app.skin.debug and a com.your.app.skin.pretty. I would not put these in the same package, because they do different things and I know I would resort to some SmurfNamingConvention (DebugAttackSurface, DebugDefenceSurface, PrettyAttackSurface etc) to create some informal namespace for the two if they were in the same package.

  • My workaround for the problem of finding related interfaces and implementations that are in separate packages is to adopt a naming configuration for my packages. E.g. I can have all my interfaces in com.your.app.skin.framework, and know that other packages on the same level of the package tree are implementations. The disadvantage is that this is an unconventional convention. Honestly, I will see how good this convention is in 6 months time :)

  • I do not use this technique religiously. There are interfaces which only make sense in a particular implementation. I don't stick them in the framework package. There are some packages where it doesn't look like I'm going to create 40 different implementation classes, so I don't bother.

  • My application uses guice and has very many interfaces.

Questions of program design generally involve benefits and drawbacks and there is no one-size-fits all answer. Like why would you ever use this technique in a tiny 200-line program? It makes sense for me, given my other architectural choices, for my specific problem. :)

The book Practical Software Engineering: A Case-Study Approach advocates for putting interfaces in seperate projects/packages.

The PCMEF+ architecture which the book talks about has the following principles:

  1. Downward Dependency Principle (DDP)
  2. Upward Notification Principle (UNP)
  3. Neighbor Communication Principle (NCP)
  4. Explicit Association Principle (EAP)
  5. Cycle Elimination Principle (CEP)
  6. Class Naming Principle (CNP)
  7. Acquaintance Package Principle (APP)

The description of principle #3 and #7 explain why it is a good idea:

The Neighbor Communication Principle demands that a package can only communicate directly with its neighbor package. This principle ensures that the system does not disintegrate to an incompressible network of intercommunicating objects. To enforce this principle, message passing between non-neighboring objects uses delegation (Section 9.1.5.1). In more complex scenarios, acquaintance package (Section 9.1.8.2) can be used to group interfaces to assist in collaboration that engages distant packages.

The Acquaintance Package Principle is the consequence of the Neighbor Communication Principle. The acquaintance package consists of interfaces that an object passes, instead of concrete objects, in arguments to method calls. The interfaces can be implemented in any PCMEF package. This effectively allows communication between non-neighboring packages while centralizing dependency management to a single acquaintance package. The need for acquaintance package was explained in Section 9.1.8.2 and is discussed again next in PCMEF context.

See this link: http://comp.mq.edu.au/books/pse/about_book/Ch9.pdf